结构型指令的职责是HTML布局。 它们塑造或重塑DOM的结构,比如添加、移除或维护这些元素。
像其它指令一样,你可以把结构型指令应用到一个宿主元素上。 然后它就可以对宿主元素及其子元素做点什么。
结构型指令非常容易识别。 在这个例子中,星号(*)被放在指令的属性名之前。
<div *ngIf="hero" >{{hero.name}}</div>
三个常用的内置结构型指令 —— NgIf、NgFor和NgSwitch...。
<div *ngIf="hero" >{{hero.name}}</div>
<ul>
<li *ngFor="let hero of heroes">{{hero.name}}</li>
</ul>
<div [ngSwitch]="hero?.emotion">
<app-happy-hero *ngSwitchCase="'happy'" [hero]="hero"></app-happy-hero>
<app-sad-hero *ngSwitchCase="'sad'" [hero]="hero"></app-sad-hero>
<app-confused-hero *ngSwitchCase="'app-confused'" [hero]="hero"></app-confused-hero>
<app-unknown-hero *ngSwitchDefault [hero]="hero"></app-unknown-hero>
</div>
<p>Hip!</p>
<ng-template>
<p>Hip!</p>
</ng-template>
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层级。
原因很简单。结构型指令可能会对宿主元素及其子元素做很复杂的事。当两个指令放在同一个元素上时,谁先谁后?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!"(欢呼声) 就是如此
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>
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>
<div>
Pick your favorite hero
(<label><input type="checkbox" checked (change)="showSad = !showSad">show sad</label>)
</div>
<select [(ngModel)]="hero">
<ng-container *ngFor="let h of heroes">
<ng-container *ngIf="showSad || h.emotion !== 'sad'">
<option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>
</ng-container>
</ng-container>
</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>