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 天就這樣結束了。