Angular的模板是动态的。在渲染的时候,会根据指令给出的指示对DOM进行转换。指令是一个带有@Directive装饰器的类。
组件型指令
通过官方文档我们可以知道@Component继承了@Directive,因此Angular中的组件其实是一种特殊的扩展指令,它在指令的基础上扩展了模板相关的内容。
结构型指令
结构型指令通过、移除或替换DOM元素来修改布局。
常见的结构型指令有: *ngIf
、 *ngFor
<li *ngFor="let hero of heroes"></li>
<app-hero-detail *ngIf="selectedHero"></app-hero-detail>
import { Component } from '@angular/core';
// Defines example component and associated template
@Component({
selector: 'example',
template: `
<div *ngFor="let f of fruit"> {{f}} </div>
<select required>
<option *ngFor="let f of fruit" [value]="f"> {{f}} </option>
</select>
`
})
// Create a class for all functions, objects, and variables
export class ExampleComponent {
// Array of fruit to be iterated by *ngFor
fruit = ['Apples', 'Oranges', 'Bananas', 'Limes', 'Lemons'];
}
<div>Apples</div>
<div>Oranges</div>
<div>Bananas</div>
<div>Limes</div>
<div>Lemons</div>
<select required>
<option value="Apples">Apples</option>
<option value="Oranges">Oranges</option>
<option value="Bananas">Bananas</option>
<option value="Limes">Limes</option>
<option value="Lemons">Lemons</option>
</select>
属性型指令
<div [class.active]="isActive"></div>
<span [style.color]="'red'"></span>
<p [attr.data-note]="'This is value for data-note attribute'">A lot of text here</p>
自定义指令
import { Directive, ElementRef, Renderer } from '@angular/core';
@Directive({
selector: '[green]'
})
export class GreenDirective {
constructor(private _elementRef: ElementRef,
private _renderer: Renderer) {
_renderer.setElementStyle(_elementRef.nativeElement, 'color', 'green');
}
}
使用方法:
<p green>A lot of green text here</p>
自定义指令测试
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
@Input('appHighlight') // tslint:disable-line no-input-rename
highlightColor: string;
constructor(private el: ElementRef) { }
@HostListener('mouseenter')
onMouseEnter() {`
this.highlight(this.highlightColor || 'red');
}
@HostListener('mouseleave')
onMouseLeave() {
this.highlight(null);
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
测试代码如下:
import { ComponentFixture, ComponentFixtrueAutoDetect, TestBed } from '@angular/core/testing';
import { Component } from '@angular/core';
import { HighlightDirective } from './highlight.directive';
@Component({
selector: 'app-test-container',
template: `
<div>
<span id="red" appHighlight>red text</span>
<span id="green" [appHighlight]="'green'">green text</span>
<span id="no">no color</span>
</div>
`
})
class ContainerComponent {}
const mouseEvents = {
get enter() {
const mouseenter = document.createEvent('MouseEvent');
mouseenter.initEvent('mouseenter', true, true);
return mouseenter;
},
get leave() {
const mouseleave = document.createEvent('MouseEvent');
mouseleave.initEvent('mouseleave', true, true);
return mouseleave;
}
};
describe('HighlightDirective', () => {
let fixture: ComponentFixture<ContainerComponent>;
let container: ContainerComponent;
let element: HTMLElement;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ContainerComponent, HightlightDirective],
providers: [
{ provide: ComponentFixtureAutoDetect, useValue: true}
]
});
fixture = TestBed.createComponent(ContainerComponent);
container = fixture.componentInstance;
element = fixture.nativeElement;
});
it('should set background-color to the empty when mouse leaves with directive without arguments', () => {
const targetElement = <HTMLSpanElement>element.querySelector('#red');
targetElement.dispatchEvent(mouseEvents.leave);
expect(targetElement.style.backgroundColor).toEqual('');
});
it('should set background-color to the empty when mouse leaves with directive with arguments', () => {
const targetElement = <HTMLSpanElement>element.querySelector('#green');
targetElement.dispatchEvent(mouseEvents.leave);
expect(targetElement.style.backgroundColor).toEqual('');
});
it('should set background-color red with no args passed', () => {
const targetElement = <HTMLSpanElement>element.querySelector('#red');
targetElement.dispatchEvent(mouseEvents.enter);
expect(targetElement.style.backgroundColor).toEqual('red');
});
it('should set background-color green when passing green parameter', () => {
const targetElement = <HTMLSpanElement>element.querySelector('#green');
targetElement.dispatchEvent(mouseEvents.enter);
expect(targetElement.style.backgroundColor).toEqual('green');
});
});
自定义结构指令
官方的unless示例:
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({ selector: '[appUnless]'})
export class UnlessDirective {
private hasView = false;
@Input()
set appUnless(condition: boolean) {
if (!condition && !this.hasView) {
this.viewContainer.createEmbeddedView(this.templateRef);
this.hasView = true;
} else if (condition && this.hasView) {
this.viewContainer.clear();
this.hasView = false;
}
}
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef) { }
}
使用unless
<p *appUnless="condition" class="unless a">
(A) This paragraph is displayed because the condition is false.
</p>
<p *appUnless="!condition" class="unless b">
(B) Although the condition is true,
this paragraph is displayed because appUnless is set to false.
</p>
通过上面的示例,我们可以看到创建结构型指令需要用到TemplateRef,ViewContainerRef。然后通过在ViewContainerRef中创建、删除TemplateRef,来控制是否选择指令标记的dom内容。结构型指令通过*号语法来使用。
参考unless的例子,我们可以结合实际需求场景,创建一个控制按钮权限的指令。
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({ selector: '[hasBtnRole]'})
export class BtnRoleDirective {
private ROLE_CODES = ['btn_1', 'btn_2'];
@Input()
set hasBtnRole(btnCode: string) {
if (!!btnCode && ROLE_CODES.includes(btnCode)) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef) { }
}