受原生 <template> 元素啟發,<ng-template> 元素讓你能宣告範本片段:一段可以動態或以程式方式算繪的內容區段。
建立範本片段
你可以在任何元件範本內以 <ng-template> 元素建立範本片段:
<p>This is a normal element</p>
<ng-template>
<p>This is a template fragment</p>
</ng-template>
當上述內容算繪時,<ng-template> 元素的內容不會算繪到頁面上。相反地,你可以取得範本片段的參照,並撰寫程式碼來動態算繪它。
片段的繫結上下文
範本片段可以包含帶有動態運算式的繫結:
@Component({
/* ... */,
template: `<ng-template>You've selected {{count}} items.</ng-template>`,
})
export class ItemCounter {
count: number = 0;
}
範本片段中的運算式或陳述式,會以宣告該片段的元件來評估,與片段實際算繪的位置無關。
取得範本片段的參照
你可以用下列三種方式之一取得範本片段的參照:
在這三種情況下,片段都以 TemplateRef 物件表示。
以範本參照變數參照範本片段
你可以為 <ng-template> 元素加上範本參照變數,以便在同一個範本檔案的其他部分參照該範本片段:
<p>This is a normal element</p>
<ng-template #myFragment>
<p>This is a template fragment</p>
</ng-template>
接著你就可以透過 myFragment 變數,在範本的其他任何地方參照這個片段。
以查詢參照範本片段
你可以使用任何元件或指令查詢 API 取得範本片段的參照。
你可以使用 viewChild 查詢直接查詢 TemplateRef 物件。
@Component({
/* ... */,
template: `
<p>This is a normal element</p>
<ng-template>
<p>This is a template fragment</p>
</ng-template>
`,
})
export class ComponentWithFragment {
templateRef = viewChild<TemplateRef<unknown>>(TemplateRef);
}
接著你就可以像其他類別成員一樣,在元件程式碼或元件範本中參照這個片段。
如果一個範本包含多個片段,你可以為每個 <ng-template> 元素加上範本參照變數來為每個片段命名,並依該名稱查詢片段:
@Component({
/* ... */,
template: `
<p>This is a normal element</p>
<ng-template #fragmentOne>
<p>This is one template fragment</p>
</ng-template>
<ng-template #fragmentTwo>
<p>This is another template fragment</p>
</ng-template>
`,
})
export class ComponentWithFragment {
fragmentOne = viewChild<TemplateRef<unknown>>('fragmentOne');
fragmentTwo = viewChild<TemplateRef<unknown>>('fragmentTwo');
}
同樣地,接著你就可以像其他類別成員一樣,在元件程式碼或元件範本中參照這些片段。
注入範本片段
如果一個指令直接套用到 <ng-template> 元素,該指令就可以注入 TemplateRef:
@Directive({
selector: '[myDirective]',
})
export class MyDirective {
private fragment = inject(TemplateRef);
}
<ng-template myDirective>
<p>This is one template fragment</p>
</ng-template>
接著你就可以像其他類別成員一樣,在指令程式碼中參照這個片段。
算繪範本片段
一旦你取得範本片段的 TemplateRef 物件參照,你可以用兩種方式之一算繪片段:在範本中使用 NgTemplateOutlet 指令,或在 TypeScript 程式碼中使用 ViewContainerRef。
使用 NgTemplateOutlet
來自 @angular/common 的 NgTemplateOutlet 指令接受一個 TemplateRef,並將片段算繪為帶有該出口之元素的同層同級元素。一般來說,你應該在 <ng-container> 元素上使用 NgTemplateOutlet。
首先,匯入 NgTemplateOutlet:
import {NgTemplateOutlet} from '@angular/common';
下列範例宣告了一個範本片段,並以 NgTemplateOutlet 將該片段算繪到 <ng-container> 元素:
<p>This is a normal element</p>
<ng-template #myFragment>
<p>This is a fragment</p>
</ng-template>
<ng-container *ngTemplateOutlet="myFragment"></ng-container>
這個範例會產生下列算繪後的 DOM:
<p>This is a normal element</p>
<p>This is a fragment</p>
使用 ViewContainerRef
檢視容器是 Angular 元件樹中的一個節點,可以容納內容。任何元件或指令都可以注入 ViewContainerRef,取得與該元件或指令在 DOM 中位置相對應的檢視容器參照。
你可以使用 ViewContainerRef 上的 createEmbeddedView 方法來動態算繪範本片段。當你以 ViewContainerRef 算繪片段時,Angular 會將它附加到 DOM 中,成為注入該 ViewContainerRef 之元件或指令的下一個同層同級元素。
下列範例展示一個元件,它接受範本片段的參照作為輸入,並在點選按鈕時將該片段算繪到 DOM 中。
@Component({
/* ... */,
selector: 'component-with-fragment',
template: `
<h2>Component with a fragment</h2>
<ng-template #myFragment>
<p>This is the fragment</p>
</ng-template>
<my-outlet [fragment]="myFragment" />
`,
})
export class ComponentWithFragment { }
@Component({
/* ... */,
selector: 'my-outlet',
template: `<button (click)="showFragment()">Show</button>`,
})
export class MyOutlet {
private viewContainer = inject(ViewContainerRef);
fragment = input.required<TemplateRef<unknown>>();
showFragment() {
this.viewContainer.createEmbeddedView(this.fragment());
}
}
在上述範例中,點選「Show」按鈕會產生下列輸出:
<component-with-fragment>
<h2>Component with a fragment</h2>
<my-outlet>
<button>Show</button>
</my-outlet>
<p>This is the fragment</p>
</component-with-fragment>
算繪範本片段時傳遞參數
以 <ng-template> 宣告範本片段時,你可以額外宣告該片段接受的參數。算繪片段時,你可以選擇傳入與這些參數相對應的 context 物件。除了參照宣告該片段之元件的資料外,你還可以在繫結運算式與陳述式中使用這個 context 物件的資料。
每個參數都寫為以 let- 為前綴的 attribute,其值對應到 context 物件中的某個屬性名稱:
<ng-template let-pizzaTopping="topping">
<p>You selected: {{ pizzaTopping }}</p>
</ng-template>
使用 NgTemplateOutlet
你可以將 context 物件繫結到 ngTemplateOutletContext 輸入:
<ng-template #myFragment let-pizzaTopping="topping">
<p>You selected: {{ pizzaTopping }}</p>
</ng-template>
<ng-container [ngTemplateOutlet]="myFragment" [ngTemplateOutletContext]="{topping: 'onion'}" />
使用 ViewContainerRef
你可以將 context 物件作為第二個引數傳給 createEmbeddedView:
this.viewContainer.createEmbeddedView(this.myFragment, {topping: 'onion'});
為範本片段提供注入器
當你算繪範本片段時,它的注入器上下文來自範本宣告的位置,而不是它被算繪的位置。你可以提供自訂注入器來覆寫這個行為。
使用 NgTemplateOutlet
你可以將自訂 Injector 傳給 ngTemplateOutletInjector 輸入:
export const THEME_DATA = new InjectionToken<string>('THEME_DATA', {
factory: () => 'light',
});
@Component({
selector: 'themed-panel',
template: `<div [class]="theme">...</div>`,
})
export class ThemedPanel {
theme = inject(THEME_DATA);
}
@Component({
selector: 'root',
imports: [NgTemplateOutlet, ThemedPanel],
template: `
<ng-template #myFragment>
<themed-panel />
</ng-template>
<ng-container *ngTemplateOutlet="myFragment; injector: customInjector" />
`,
})
export class Root {
customInjector = Injector.create({
providers: [{provide: THEME_DATA, useValue: 'dark'}],
});
}
繼承出口的注入器
你可以將 ngTemplateOutletInjector 設定為字串 'outlet',讓內嵌檢視從出口在 DOM 中的位置繼承其注入器,而不是從範本宣告的位置繼承。
<ng-template #node let-items>
<item-component>
@for (child of items; track $index) {
<ng-container
*ngTemplateOutlet="node; context: {$implicit: child.children}; injector: 'outlet'"
/>
}
</item-component>
</ng-template>
<ng-container *ngTemplateOutlet="node; context: {$implicit: topLevelItems}" />
node 範本的每一次遞迴算繪,都會從周圍的 <item-component> 繼承注入器,讓每個巢狀層級都能存取限定於其父元件的 providers。
NOTE: 這在建立遞迴結構,或算繪的範本需要存取出口位置之元件樹中的 providers 時很有用。
使用 ViewContainerRef
你可以將自訂注入器作為選項物件的一部分傳給 createEmbeddedView:
this.viewContainer.createEmbeddedView(this.myFragment, context, {
injector: myCustomInjector,
});
結構指令
結構指令是任何符合下列條件的指令:
- 注入
TemplateRef - 注入
ViewContainerRef,並以程式方式算繪注入的TemplateRef
Angular 支援結構指令的特殊便利語法。如果你將指令套用到某個元素,並在該指令的選擇器前加上星號(*)字元,Angular 會將整個元素及其全部內容解讀為範本片段:
<section *myDirective>
<p>This is a fragment</p>
</section>
這等同於:
<ng-template myDirective>
<section>
<p>This is a fragment</p>
</section>
</ng-template>
開發者通常使用結構指令來依條件算繪片段,或多次算繪片段。
如需更多細節,請參閱結構指令。
其他資源
如需了解其他函式庫如何使用 ng-template 的範例,請參閱:
- Angular Material 的 Tabs:在分頁啟用之前,不會有任何內容算繪到 DOM 中
- Angular Material 的 Table:讓開發者能定義不同的資料算繪方式