结构型指令的职责是HTML布局。 它们塑造或重塑DOM的结构,比如添加、移除或维护这些元素。
像其它指令一样,你可以把结构型指令应用到一个宿主元素上。 然后它就可以对宿主元素及其子元素做点什么。
结构型指令非常容易识别。 在这个例子中,星号(*)被放在指令的属性名之前。
<div *ngIf="hero" >{{hero.name}}</div>
三个常用的内置结构型指令 —— NgIf、NgFor和NgSwitch...。
1 <div *ngIf="hero" >{{hero.name}}</div> 2 3 <ul> 4 <li *ngFor="let hero of heroes">{{hero.name}}</li> 5 </ul> 6 7 <div [ngSwitch]="hero?.emotion"> 8 <app-happy-hero *ngSwitchCase="'happy'" [hero]="hero"></app-happy-hero> 9 <app-sad-hero *ngSwitchCase="'sad'" [hero]="hero"></app-sad-hero> 10 <app-confused-hero *ngSwitchCase="'app-confused'" [hero]="hero"></app-confused-hero> 11 <app-unknown-hero *ngSwitchDefault [hero]="hero"></app-unknown-hero> 12 </div>
ngIf内幕
<div *ngIf="hero" >{{hero.name}}</div> <ng-template [ngIf]="hero"> <div>{{hero.name}}</div> </ng-template>
*ngFor
内幕
<div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd">
({{i}}) {{hero.name}}
</div>
<ng-template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById">
<div [class.odd]="odd">({{i}}) {{hero.name}}</div>
</ng-template>
每个宿主元素上只能有一个结构型指令
有时我们会希望只有当特定的条件为真时才重复渲染一个 HTML 块。 你可能试过把*ngFor
和*ngIf
放在同一个宿主元素上,但Angular 不允许。这是因为我们在一个元素上只能放一个结构型指令。
原因很简单。结构型指令可能会对宿主元素及其子元素做很复杂的事。当两个指令放在同一个元素上时,谁先谁后?NgIf
优先还是NgFor
优先?NgIf
可以取消NgFor
的效果吗? 如果要这样做,Angular 应该如何把这种能力泛化,以取消其它结构型指令的效果呢?
对这些问题,没有办法简单回答。而禁止多个结构型指令则可以简单地解决这个问题。 这种情况下有一个简单的解决方案:把*ngIf
放在一个"容器"元素上,再包装进 *ngFor
元素。 这个元素可以使用ng-container
,以免引入一个新的HTML层级。
NgSwitch
内幕
Angular 的 NgSwitch
实际上是一组相互合作的指令:NgSwitch
、NgSwitchCase
和 NgSwitchDefault
。
星号(*
)语法比不带语法糖的形式更加清晰。 如果找不到单一的元素来应用该指令,可以使用<ng-container>作为该指令的容器。
<ng-template>指令
<ng-template>是一个 Angular 元素,用来渲染HTML。 它永远不会直接显示出来。 事实上,在渲染视图之前,Angular 会把<ng-template>
及其内容替换为一个注释。
如果没有使用结构型指令,而仅仅把一些别的元素包装进<ng-template>
中,那些元素就是不可见的。 在下面的这个短语"Hip! Hip! Hooray!"中,中间的这个 "Hip!"(欢呼声) 就是如此
<p>Hip!</p> <ng-template> <p>Hip!</p> </ng-template> <p>Hooray!</p>
<ng-container> 的救赎
Angular的<ng-container>
是一个分组元素,但它不会污染样式或元素布局,因为 Angular 压根不会把它放进 DOM 中。
<p> I turned the corner <ng-container *ngIf="hero"> and saw {{hero.name}}. I waved </ng-container> and continued on my way. </p>
1 <div> 2 Pick your favorite hero 3 (<label><input type="checkbox" checked (change)="showSad = !showSad">show sad</label>) 4 </div> 5 <select [(ngModel)]="hero"> 6 <ng-container *ngFor="let h of heroes"> 7 <ng-container *ngIf="showSad || h.emotion !== 'sad'"> 8 <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option> 9 </ng-container> 10 </ng-container> 11 </select>
自定义结构性指令
component.ts
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core'; /** * Add the template content to the DOM unless the condition is true. */ @Directive({ selector: '[appUnless]'}) export class UnlessDirective { private hasView = false; constructor( private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef) { } @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; } } }
component.html
<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 myUnless is set to false. </p>