Angular的模板是动态的。在渲染的时候,会根据指令给出的指示对DOM进行转换。指令是一个带有@Directive装饰器的类。

组件型指令

通过官方文档我们可以知道@Component继承了@Directive,因此Angular中的组件其实是一种特殊的扩展指令,它在指令的基础上扩展了模板相关的内容。

结构型指令

结构型指令通过、移除或替换DOM元素来修改布局。
常见的结构型指令有: *ngIf*ngFor

  1. <li *ngFor="let hero of heroes"></li>
  2. <app-hero-detail *ngIf="selectedHero"></app-hero-detail>
  1. import { Component } from '@angular/core';
  2. // Defines example component and associated template
  3. @Component({
  4. selector: 'example',
  5. template: `
  6. <div *ngFor="let f of fruit"> {{f}} </div>
  7. <select required>
  8. <option *ngFor="let f of fruit" [value]="f"> {{f}} </option>
  9. </select>
  10. `
  11. })
  12. // Create a class for all functions, objects, and variables
  13. export class ExampleComponent {
  14. // Array of fruit to be iterated by *ngFor
  15. fruit = ['Apples', 'Oranges', 'Bananas', 'Limes', 'Lemons'];
  16. }
  1. <div>Apples</div>
  2. <div>Oranges</div>
  3. <div>Bananas</div>
  4. <div>Limes</div>
  5. <div>Lemons</div>
  6. <select required>
  7. <option value="Apples">Apples</option>
  8. <option value="Oranges">Oranges</option>
  9. <option value="Bananas">Bananas</option>
  10. <option value="Limes">Limes</option>
  11. <option value="Lemons">Lemons</option>
  12. </select>

属性型指令

  1. <div [class.active]="isActive"></div>
  2. <span [style.color]="'red'"></span>
  3. <p [attr.data-note]="'This is value for data-note attribute'">A lot of text here</p>

自定义指令

  1. import { Directive, ElementRef, Renderer } from '@angular/core';
  2. @Directive({
  3. selector: '[green]'
  4. })
  5. export class GreenDirective {
  6. constructor(private _elementRef: ElementRef,
  7. private _renderer: Renderer) {
  8. _renderer.setElementStyle(_elementRef.nativeElement, 'color', 'green');
  9. }
  10. }

使用方法:

  1. <p green>A lot of green text here</p>

自定义指令测试

  1. import { Directive, ElementRef, HostListener, Input } from '@angular/core';
  2. @Directive({
  3. selector: '[appHighlight]'
  4. })
  5. export class HighlightDirective {
  6. @Input('appHighlight') // tslint:disable-line no-input-rename
  7. highlightColor: string;
  8. constructor(private el: ElementRef) { }
  9. @HostListener('mouseenter')
  10. onMouseEnter() {`
  11. this.highlight(this.highlightColor || 'red');
  12. }
  13. @HostListener('mouseleave')
  14. onMouseLeave() {
  15. this.highlight(null);
  16. }
  17. private highlight(color: string) {
  18. this.el.nativeElement.style.backgroundColor = color;
  19. }
  20. }

测试代码如下:

  1. import { ComponentFixture, ComponentFixtrueAutoDetect, TestBed } from '@angular/core/testing';
  2. import { Component } from '@angular/core';
  3. import { HighlightDirective } from './highlight.directive';
  4. @Component({
  5. selector: 'app-test-container',
  6. template: `
  7. <div>
  8. <span id="red" appHighlight>red text</span>
  9. <span id="green" [appHighlight]="'green'">green text</span>
  10. <span id="no">no color</span>
  11. </div>
  12. `
  13. })
  14. class ContainerComponent {}
  15. const mouseEvents = {
  16. get enter() {
  17. const mouseenter = document.createEvent('MouseEvent');
  18. mouseenter.initEvent('mouseenter', true, true);
  19. return mouseenter;
  20. },
  21. get leave() {
  22. const mouseleave = document.createEvent('MouseEvent');
  23. mouseleave.initEvent('mouseleave', true, true);
  24. return mouseleave;
  25. }
  26. };
  27. describe('HighlightDirective', () => {
  28. let fixture: ComponentFixture<ContainerComponent>;
  29. let container: ContainerComponent;
  30. let element: HTMLElement;
  31. beforeEach(() => {
  32. TestBed.configureTestingModule({
  33. declarations: [ContainerComponent, HightlightDirective],
  34. providers: [
  35. { provide: ComponentFixtureAutoDetect, useValue: true}
  36. ]
  37. });
  38. fixture = TestBed.createComponent(ContainerComponent);
  39. container = fixture.componentInstance;
  40. element = fixture.nativeElement;
  41. });
  42. it('should set background-color to the empty when mouse leaves with directive without arguments', () => {
  43. const targetElement = <HTMLSpanElement>element.querySelector('#red');
  44. targetElement.dispatchEvent(mouseEvents.leave);
  45. expect(targetElement.style.backgroundColor).toEqual('');
  46. });
  47. it('should set background-color to the empty when mouse leaves with directive with arguments', () => {
  48. const targetElement = <HTMLSpanElement>element.querySelector('#green');
  49. targetElement.dispatchEvent(mouseEvents.leave);
  50. expect(targetElement.style.backgroundColor).toEqual('');
  51. });
  52. it('should set background-color red with no args passed', () => {
  53. const targetElement = <HTMLSpanElement>element.querySelector('#red');
  54. targetElement.dispatchEvent(mouseEvents.enter);
  55. expect(targetElement.style.backgroundColor).toEqual('red');
  56. });
  57. it('should set background-color green when passing green parameter', () => {
  58. const targetElement = <HTMLSpanElement>element.querySelector('#green');
  59. targetElement.dispatchEvent(mouseEvents.enter);
  60. expect(targetElement.style.backgroundColor).toEqual('green');
  61. });
  62. });

自定义结构指令

官方的unless示例:

  1. import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
  2. @Directive({ selector: '[appUnless]'})
  3. export class UnlessDirective {
  4. private hasView = false;
  5. @Input()
  6. set appUnless(condition: boolean) {
  7. if (!condition && !this.hasView) {
  8. this.viewContainer.createEmbeddedView(this.templateRef);
  9. this.hasView = true;
  10. } else if (condition && this.hasView) {
  11. this.viewContainer.clear();
  12. this.hasView = false;
  13. }
  14. }
  15. constructor(
  16. private templateRef: TemplateRef<any>,
  17. private viewContainer: ViewContainerRef) { }
  18. }

使用unless

  1. <p *appUnless="condition" class="unless a">
  2. (A) This paragraph is displayed because the condition is false.
  3. </p>
  4. <p *appUnless="!condition" class="unless b">
  5. (B) Although the condition is true,
  6. this paragraph is displayed because appUnless is set to false.
  7. </p>

通过上面的示例,我们可以看到创建结构型指令需要用到TemplateRef,ViewContainerRef。然后通过在ViewContainerRef中创建、删除TemplateRef,来控制是否选择指令标记的dom内容。结构型指令通过*号语法来使用。

参考unless的例子,我们可以结合实际需求场景,创建一个控制按钮权限的指令。

  1. import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
  2. @Directive({ selector: '[hasBtnRole]'})
  3. export class BtnRoleDirective {
  4. private ROLE_CODES = ['btn_1', 'btn_2'];
  5. @Input()
  6. set hasBtnRole(btnCode: string) {
  7. if (!!btnCode && ROLE_CODES.includes(btnCode)) {
  8. this.viewContainer.createEmbeddedView(this.templateRef);
  9. } else {
  10. this.viewContainer.clear();
  11. }
  12. }
  13. constructor(
  14. private templateRef: TemplateRef<any>,
  15. private viewContainer: ViewContainerRef) { }
  16. }