viewChild 函數的另一個高階用例是將 NgTemplate 嵌入到 ViewConatinerRef 中。當範本非常簡單到擁有一個組件就顯得有些過分時,我們可以透過程式設計方式附加一個 NgTemplate 而不是組件。嵌入 NgTemplate 的工作類似於建立動態組件。
我將重複第 18 天完成的示範,但這一次,App 組件附加 NgTemplate 而不是 StarWarCharacterComponent。此示範使用 viewChild 函數來查詢 ViewContainerRef 並呼叫 createEmbeddedView 方法。 createEmbeddedView 方法接受 TemplateRef 和選擇性的 template context。
import { provideExperimentalZonelessChangeDetection } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
export const appConfig = {
providers: [
provideHttpClient(),
provideExperimentalZonelessChangeDetection()
]
}
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app.config';
bootstrapApplication(App, appConfig);
提供 Http client 和 experiental zoneless feature,並引導應用程式設定。
import { catchError, map, of, mergeMap, forkJoin } from 'rxjs';
import { inject, runInInjectionContext, Injector } from '@angular/core';
import { HttpClient } from '@angular/common/http';
export type Person = {
name: string;
height: string;
mass: string;
hair_color: string;
skin_color: string;
eye_color: string;
gender: string;
films: string[];
}
const URL = 'https://swapi.dev/api/people';
export function getPerson(id: number, injector: Injector) {
return runInInjectionContext(injector, () => {
const http = inject(HttpClient);
return http.get<Person>(`${URL}/${id}`).pipe(
catchError((err) => {
console.error(err);
return of(undefined);
}));
});
}
getPerson 函數透過 id 檢索星際大戰角色。`
<ng-template #starWar let-id let-person="person" let-isSith="isSith">
<div class="border">
@if(person) {
<p>Id: {{ id }} </p>
@if (isSith) {
<p>Is a Sith. He is evil.</p>
}
<p>Name: {{ person.name }}</p>
<p>Height: {{ person.height }}</p>
<p>Mass: {{ person.mass }}</p>
<p>Hair Color: {{ person.hair_color }}</p>
<p>Skin Color: {{ person.skin_color }}</p>
<p>Eye Color: {{ person.eye_color }}</p>
<p>Gender: {{ person.gender }}</p>
} @else {
<p>No info</p>
}
</div>
</ng-template>
ngTemplate 有一個範本變數 (template variable) starWar、一個隱式變數 (let-id) 和兩個命名變數(let-person 和 let-isSith)。 let-person 綁定到 person 屬性,而 let-isSith 則綁定到 isSith 屬性。 此範本顯示星際大戰角色的 id 和詳細資訊。 如果 isSith 為 true,範本將顯示文字 "Is a Sith, He is evil."。
Component({
selector: 'app-root',
standalone: true,
imports: [FormsModule],
template: `
<div class="container">
<ng-container #vcr />
</div>
<select [(ngModel)]="jediId">
<option value="1">Luke</option>
<option value="10">Obi Wan Kenobe</option>
<option value="20">Yoda</option>
</select>
<button (click)="addAJedi(jediId())">Add a Jedi</button>
<select [(ngModel)]="sithId">
<option value="4">Darth Vader</option>
<option value="44">Darth Maul</option>
</select>
<button (click)="addAJedi(sithId(), true)">Add a Sith</button>`,
<ng-template #starWar>...</ng-template>`,
})
export class App implements OnDestroy {
jediId = signal(1);
sithId = signal(4);
ngOnDestroy(): void {}
}
應用程式組件由 Jedi 和 Sith 下拉列表組成。 Jedi 列表的 NgModel 綁定到 jediId signal,Sith 列表的 NgModel 綁定到 sithId signal。當使用者按一下 "Add a Jedi" 按鈕時,該元件會呼叫 addAJedi 方法將範本嵌入到 ViewContainerRef 中。同樣,使用者點擊 "Add a Sith" 按鈕來呼叫相同的方法將範本嵌入到 ViewContainerRef 中。
<ng-container #vcr />
NgContainer 有一個範本變數 vcr,viewChild 函數使用它來查詢 ViewContainerRef。
vcr = viewChild.required('vcr', { read: ViewContainerRef });
vcr 的類型是 Signal<ViewContainerRef>,因為 read 屬性會擷取 ViewContainerRef。
templateRef = viewChild.required('starWar', { read: TemplateRef });
viewChild 函數查詢 starWar 並檢索 TemplateRef 。
embeddedViewRefs = [] as EmbeddedViewRef<any>[];
async addAJedi(id: number, isSith = false) {
const person = await lastValueFrom(getPerson(id, this.injector));
const context = {
$implicit: id,
isSith,
person,
};
const embeddedViewRef = this.vcr().createEmbeddedView(this.templateRef(), context);
this.embeddedViewRefs.push(embeddedViewRef);
}
addJedi 方法呼叫 getPerson 函數並使用 RxJS 運算子 lastValueFrom 來檢索 Star War 角色。 然後,context 儲存隱式屬性和命名屬性。 createEmbeddedView 方法將 TemplateRef 附加到 ViewContainerRer 並傳回 embeddedViewRef。 將 embeddedViewRef 附加到要在ngDestroy lifecycle hook 中銷毀 embeddedViewRefs 陣列。
ngOnDestroy(): void {
if (this.embeddedViewRefs) {
for (const ref of this.embeddedViewRefs) {
ref.destroy();
}
}
}
當應用程式銷毀 App 組件時,ngOnDestroy 會釋放 embeddedViewRefs 的 memory 以避免 memory leak。
viewChild 可以查詢 ViewContainerRef,並且 ViewContainerRef 可以呼叫 createEmbeddedView 方法以程式設計方式附加 ngTemplate。createEmbddedView 接受 TemplateRef 和 template context, ngTemplate 可以存取屬性以顯示其值。NgTemplate。否則,請使用 Angular 組件。鐵人賽的第 19 天就這樣結束了。