構造ディレクティブ(*ngIf/*ngForのような*がついたもの)は、要素を追加または削除することで DOM を変更します。はシンタックスシュガーで、内部ではAngularが要素をでラップし、ディレクティブがそのテンプレートをいつ/どのようにレンダリングするかを制御します。
*<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>
2つのインジェクトされた部分が重要です。**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を使用)を構築できます。例えば、権限ベースのレンダリング、カスタムリピーター、遅延印字などです。
また、1つの要素に2つの構造ディレクティブ(*ngIfと*ngFor)を置けない理由も明確になります。各ディレクティブは独自のテンプレートラッパーが必要です。