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 variablesexport class ExampleComponent {// Array of fruit to be iterated by *ngForfruit = ['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-renamehighlightColor: 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) { }}
