结构型指令(带有 * 的指令,如 *ngIf/*ngFor)通过添加或移除元素来改变 DOM。* 是语法糖——在幕后,Angular 将元素包装在 中,指令控制该模板何时/如何渲染到 DOM 中。
<ng-template><!-- what you write -->
<p *ngIf="isVisible">Hello</p>
<!-- what Angular actually does -->
<ng-template [ngIf]="isVisible">
<p>Hello</p>
</ng-template>
* 告诉 Angular:"把这个元素包装在一个模板中,让指令决定何时将其注入到 DOM 中。" <ng-template> 是一块惯性标记,在指令实例化它之前不会被渲染。
import { Directive, Input, TemplateRef, ViewContainerRef } from "@angular/core";
@Directive({ selector: "[appUnless]" }) // *appUnless = "the opposite of ngIf"
export class UnlessDirective {
constructor(
private templateRef: TemplateRef<any>, // the wrapped <ng-template>
private viewContainer: ViewContainerRef, // where to render it
) {}
@Input() set appUnless(condition: boolean) {
if (!condition) {
this.viewContainer.createEmbeddedView(this.templateRef); // render it
} else {
this.viewContainer.clear(); // remove it
}
}
}
// usage: <p *appUnless="isHidden">Shown when isHidden is false</p>
这两个注入的部分是关键:TemplateRef 是元素的模板("渲染什么"),而 ViewContainerRef 是 DOM 中的位置("在哪里")。指令调用 createEmbeddedView 来添加元素,或调用 clear 来移除它。
<!-- newer Angular replaces *ngIf/*ngFor with built-in block syntax -->
@if (isVisible) { <p>Hello</p> } @else { <p>Hidden</p> }
@for (item of items; track item.id) { <li>{{ item.name }}</li> }
这种较新的语法更易读且性能更好,尽管底层的添加/移除概念是相同的。
理解 * → <ng-template> 的脱语法糖可以揭示 *ngIf/*ngFor 如何添加和移除 DOM 元素,并让你能够构建自定义结构型指令(使用 TemplateRef + ViewContainerRef)来进行高级模板控制——例如基于权限的渲染、自定义重复器或懒加载注入。
它还说明了为什么不能在一个元素上放置两个结构型指令(*ngIf 和 *ngFor):每个都需要自己的模板包装器。