结构型指令

结构型指令的职责是Html布局,他们塑造或重塑dom结构,比如添加、移除或维护元素。

一个宿主元素上可以应用多个属性型指令,但只能应用一个结构型指令。

例如:*ngIf*ngFor*ngSwitch

星号前缀

星号是个用来简化更复杂语法的语法糖,这个字符串是一个微语法,而不是模板表达式。Angular会解开这个语法糖,变成一个<ng-template>标记,包裹着宿主元素及其子元素。

指令的类名拼写成大驼峰形式,而它的属性名则拼写成小驼峰形式。

  • NgIf

    NgIf接收一个布尔值,并据此让一整块dom树出现或消失。

此处的消失并不是使用css将元素隐藏,而是直接将这些元素从dom中物理删除。

  1. <div *ngIf="hero" class="name">{{hero.name}}</div>
  2. <!-- Angular翻译后 -->
  3. <ng-template [ngIf]='hero'>
  4. <div class='name'>
  5. {{hero.name}}
  6. </div>
  7. </ng-template>
  • NgFor ```html
    ({{i}}) {{hero.name}}

({{i}}) {{hero.name}}

  1. > - let 关键字声明一个模板输入变量,解析器会把let herolet ilet odd翻译成命名变量:let-herolet-ilet-odd
  2. > - 微语法解析器接收oftrackBy,把他们首字母大写(of -> OftrackBy -> TrackBy),并且给他们加上指令的属性名(ngFor)前缀,最终形成的名字是 ngForOfngForTrackBy 。这两个最终形成的名字是NgFor的输入属性,指令据此了解列表是heroes,而track-by函数是trackById
  3. > - NgFor指令在列表上循环,每次循环中都会设置和重置它自己的上下文对象上的属性。这些属性包括但不限于 index odd 以及一个特殊的属性名 $implicit(隐式变量)。
  4. > - let-i let-odd 变量是通过 let i = index let odd = odd 来定义的。Angular把他们设置为上下文对象中的indexodd属性的当前值。
  5. > - 这里并没有指定let-hero的上下文属性。它的来源时隐式的。Angularlet-hero设置为此上下文中$implicit属性的值,它是由NgFor用当前迭代中的内容初始化的。
  6. -
  7. NgSwitch
  8. > AngularNgSwitch实际上是一组互相合作的指令:NgSwitchNgSwitchCaseNgSwitchDefault
  9. > NgSwitch本身不是结构型指令,而是一个属性型指令,它控制其他两个switch指令的行为,这也就是为什么要写成`[ngSwitch]`而不是`*ngSwitch`的原因。
  10. > NgSwitchCaseNgSwitchDefault都是结构型指令,因袭㤇使用星号前缀来把他们附着到元素上。
  11. ```html
  12. <div [ngSwitch]="hero?.emotion">
  13. <app-happy-hero *ngSwitchCase="'happy'" [hero]="hero"></app-happy-hero>
  14. <app-sad-hero *ngSwitchCase="'sad'" [hero]="hero"></app-sad-hero>
  15. <app-confused-hero *ngSwitchCase="'confused'" [hero]="hero"></app-confused-hero>
  16. <app-unknown-hero *ngSwitchDefault [hero]="hero"></app-unknown-hero>
  17. </div>
  18. <!-- Angular翻译后 -->
  19. <div [ngSwitch]="hero?.emotion">
  20. <ng-template [ngSwitchCase]="'happy'">
  21. <app-happy-hero [hero]="hero"></app-happy-hero>
  22. </ng-template>
  23. <ng-template [ngSwitchCase]="'sad'">
  24. <app-sad-hero [hero]="hero"></app-sad-hero>
  25. </ng-template>
  26. <ng-template [ngSwitchCase]="'confused'">
  27. <app-confused-hero [hero]="hero"></app-confused-hero>
  28. </ng-template >
  29. <ng-template ngSwitchDefault>
  30. <app-unknown-hero [hero]="hero"></app-unknown-hero>
  31. </ng-template>
  32. </div>

微语法

有限使用星号语法:星号语法比不带语法糖的形式更加清晰,如果找不到单一的元素来应用该指令,可以使用<ng-container>作为该指令的容器。

约束

使用微语法必须满足以下要求:

  • 它必须可被预先了解,以便IDE可以解析它而无需知道指令的底层语义或已存在的哪些指令
  • 它必须转换为dom中的“键-值”属性

语法

编写自己的结构型指令时,使用一下语法

  1. *:prefix="( :let | :expression ) (';' | ',')? ( :let | :as | :keyExp)*"

其中:

prefix Html属性键(attribute key)
key Html属性键(attribute key)
local 模板中使用的局部变量名
export 指令使用指定名称导出的值
expression 标准Angular表达式
keyExp = :key “:”? :expression (“as” :local)? “;”?
let = “let” :local “=” :export “;”?
as = :export “as” :local “;”?

翻译

将微语法转换成常规的绑定语法:

微语法 翻译结果
prefix和裸表达式 [prefix]=”expression”
keyExp [prefixKey] “表达式” (let-prefixKey=”export”) 注意prefix已经加成了key
let let-local=”export”

微语法样例

微语法 解语法糖后
*ngFor=”let item of [1,2,3]”
*ngFor=”let item of [1,2,3] as items; trackBy: myTrack; index as i”
*ngIf=”exp”
*ngIf=”exp as value”

模板输入变量

模板输入变量是这样一种变量,你可以在单个实例的模板中引用它的值。这个例子中有好几个模板输入变量:hero和odd。它们都是使用let作为前导关键字。

模板输入变量和模板引用变量是不同的,无论是语义上还是语法上。

使用let关键字(如let hero)在模板中声明一个模板输入变量,这个变量的范围被限制在所重复模板的单一实例上。事实上,可以在其他结构型指令中使用同样的变量名。

而声明模板引用变量使用的是个变量名加#前缀的方式,(#var),一个引用变量引用的是它所附着的元素、组件或指令。它可以在整个模板的任一位置访问。

模板输入变量和引用变量具有各自独立的命名空间,let hero中的hero和#hero中的hero不是同一个变量。

ng-template

<ng-template>是一个Angular元素,用来渲染Html。它永远不会直接显示出来。在渲染视图之前,Angular会把<ng-template>及其内容替换为一个注释。

如果没有使用结构型指令,而仅仅把一些别的元素包装进<ng-template>中,那些元素就是不可见的。

  1. <p>Hip!</p>
  2. <!-- 此处的ng-template在渲染时会被替换为一个注释,不会显示出来 -->
  3. <ng-template>
  4. <p>Hip!</p>
  5. </ng-template>
  6. <p>Hooray!</p>

ng-container

Angular的<ng-container>是一个分组元素,但是它并不会污染样式或布局,因为angular压根不会把它放进dom中。

<ng-container>更像是if/for语句的大括号,用于将后面执行的内容装进一个代码块,而自己本身并不会被渲染显示成Html标签。在需要用*ngIf*ngFor但又不想加入其它html标签时,可以使用<ng-container>标签配置相关的结构型指令。

  1. <p>
  2. I turned the corner
  3. <ng-container *ngIf="hero">
  4. and saw {{hero.name}}. I waved
  5. </ng-container>
  6. and continued on my way.
  7. </p>
  8. <div>
  9. Pick your favorite hero
  10. (<label><input type="checkbox" checked (change)="showSad = !showSad">show sad</label>)
  11. </div>
  12. <select [(ngModel)]="hero">
  13. <ng-container *ngFor="let h of heroes">
  14. <ng-container *ngIf="showSad || h.emotion !== 'sad'">
  15. <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>
  16. </ng-container>
  17. </ng-container>
  18. </select>

编写结构型指令

编写一个结构型指令,作用于ngIf相反:传入true时宿主元素隐藏,传入false时宿主元素显示

  1. 创建一个指令(使用命令ng g directive unless或者手动新建一个unless.directive.ts)

    • import进来Directive符号,用于使用@Directive装饰器

    • import进来Input符号,用于后面使用@Input装饰器接收传进来的值

    • import进来TemplateRef符号,用于注入内嵌视图

    • import进来ViewContainerRef符号,用于注入视图容器

      简单结构型指令会从Angular生成的<ng-template>元素中创建一个内嵌的视图,并把这个视图插入一个视图容器中。

可以使用TemplateRef取得<ng-template>的内嵌视图内容。

可以使用ViewContainerRef访问这个视图容器。

  • 将TemplateRef、ViewContainerRef注入到类的构造器中

  • 在class上使用@Directive装饰器声明该类是一个指令

  • 在@Directive的selector中声明该指令的选择器

  • 使用@Input获取传入的appUnless的值,并进行判断处理

  1. import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
  2. @Directive({
  3. selector: '[appUnless]' // 声明该指令的选择器
  4. })
  5. export class UnlessDirective {
  6. private hasView: boolean = false;
  7. // 注入TemplateRef、ViewContainerRef
  8. constructor(
  9. private templateRef: TemplateRef<any>,
  10. private viewContainerRef: ViewContainerRef) {
  11. }
  12. // 使用@Input获取appUnless的值
  13. @Input() set appUnless(condition: boolean) {
  14. if(!condition && !this.hasView) {
  15. // 当condition为false时,将ng-template内嵌视图插入视图容器进行展示
  16. this.viewContainerRef.createEmbeddedView(this.templateRef);
  17. this.hasView = true;
  18. } else if(condition && this.hasView) {
  19. // 当condition为true时,清空视图容器
  20. this.viewContainerRef.clear();
  21. this.hasView = false;
  22. }
  23. }
  24. }
  1. 在对应的module中声明该指令

  2. 在组件中定义viewHidden变量,并在组件对应的html模板中使用该指令 ```html

    这句话被隐藏

    这句话会展示

使用ng-template展示

```