iT邦幫忙

2024 iThome 鐵人賽

DAY 21
0
JavaScript

Signal API in Angular系列 第 21

Day 21 - contentChild 函數介紹

  • 分享至 

  • xImage
  •  

Today, I will introduce contentChild that is the signal counterpart of @ContentChild decorator.

ContentChild 裝飾器和 contentChild 函數的區別

  • ContentChild 裝飾器傳回一個實例,而 contentChild 函數傳回一個訊號。
  • ContentChild 裝飾器的 static 屬性從 contentChild 函數中刪除。

contentChild 有 2 個變體: conentChild.required()contentChild()

必需的 contentChild 和可選的 contentChild

  • contentChild.required() 表示該元素在範本中至少出現一次,並且檢索第一個元素。 此函數的傳回類型是 Signal<T>
  • contentChild() 表示該元素未出現在範本中且可能是未定義的。函數的傳回類型為 Signal<T|undefined>

在下面的例子中,我展示了 contentChild 如何透過範本變數 (template variables)、ngTemplates 和 Angular 組件查詢元素。

例子 1:透過範本變數 (template variables) 查詢元素

import { Component, contentChild, effect, ElementRef, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
 selector: 'app-query-by-variable',
 standalone: true,
 imports: [FormsModule],
 template: `
   <div class="container">
     <h3>Query ContentChild by variables</h3>
     <label for="color">
       <span>Color: </span>
       <select id="color" name="color" [(ngModel)]="color">
         <option value="">----</option>
         <option value="red">red</option>
         <option value="yellow">yellow</option>
       </select>
     </label>
     <ng-content select="[header]">Default Header</ng-content>
     <ng-content></ng-content>
     <ng-content select="[footer]">Default Footer</ng-content>
   </div>
 `,
})
export class AppQueryByVariableComponent {
 color = signal('');
}

AppQueryByVaraibleComponent 元件有三個 <ng-content /> 組件,分別用於頁首、頁尾和正文。 它還具有一個下拉式選單來選擇元素的背景顏色。

<app-query-by-variable>
     <div #header header class="projection">Custom Header 2</div>
     <div #body class="projection">Custom Body</div>
     <div #footer footer class="projection">
       <ul>
         <li>Custom Footer</li>
         <li>Custom Footer 2</li>
       </ul>
     </div>
</app-query-by-variable>

App 組件中,div 元素被投影到 <app-query-by-variable><ng-content>。第一個 div 元素的範本變數是 #header,並投影到命名 headerng-content。第二個 div 元素的範本變數是#body,並投影到預設的 ng-content。最後一個 div 元素的範本變數是 #header,並投影到命名 footerng-content

header = contentChild('header', { read: ElementRef });
footer = contentChild('footer', { read: ElementRef });
body = contentChild('body', { read: ElementRef });

contentChild 函數透過範本變數查詢元素。 第二個參數 { read: ElementRef } 期望函數會擷取 ElementRef

 constructor() {
   effect(() => {
     const color = this.color();

     const header = this.header()?.nativeElement;
     const footer = this.footer()?.nativeElement;
     const body = this.body()?.nativeElement;

     if (header) {
       header.style.backgroundColor = color;
     }

     if (footer) {
       footer.style.backgroundColor = color;
     }

     if (body) {
       body.style.backgroundColor = color;
     }
   });
 }

color signal 更新時,effect 會執行邏輯來變更背景顏色的 CSS 樣式。我使用了 contentChild 而不是 contentChild.required;因此, signal 值可能是 undefined 的。每個 if-block 在將顏色指派給 backgroundColor 屬性之前都會比較 signal 是否 defined

例子 2:透過 contentChild 查詢 ngTemplates

@Directive({
 selector: '[someDirective]',
 standalone: true,
})
export class AppSomeDirective {
 template = contentChild.required(TemplateRef);
}

我們可以寫一個 AppSomeDirective 指令 (directive),使用 contentChild 來查詢投影的 ngTemplate。

@Component({
 selector: 'app-query-by-type',
 standalone: true,
 imports: [AppSomeDirective, NgTemplateOutlet],
 template: `
   <div class="container">
     <h3>Query ContentChild by TemplateRef</h3>
     <div someDirective>
       <ng-template>
         <p>Projected item</p>
         <p>Projected item 2</p>
         <p>Projected Item 3</p>
       </ng-template>
     </div>
     <ng-content>Default Header</ng-content>
     <ng-container *ngTemplateOutlet="template()"></ng-container>
     <ng-container *ngTemplateOutlet="directive().template()"></ng-container>
   </div>
 `,
 })
export class AppQueryByDirectiveComponent {}

div 元素有一個 someDirective 屬性;因此,directive 的 contentChild 可以檢索由三個段落元素組成的 ngTemplate。該組件還有一個預設的 <ng-content>,它可以使用 contentChild 查詢另一個 ngTemplate。

<ng-container *ngTemplateOutlet="template()"></ng-container>
<ng-container *ngTemplateOutlet="directive().template()"></ng-container>

ngTemplates 被指派給 ngTemplateOutlet 指令來呈現動態內容。

export class AppQueryByDirectiveComponent {
 directive = viewChild.required(AppSomeDirective);
 template = contentChild.required(TemplateRef);
}

AppQueryByDirectiveComponent 元件使用 viewChild 函數來查詢指令 (directive) 並存取其 contentChildcontentChild.required 查詢預設 <ng-content> 中存在的 TemplateRef

<app-query-by-type>
   <ng-template>Custom Header 3</ng-template>
</app-query-by-type>

App 元件中,ngTemplate 被投影到 <app-query-by-type> 中,這樣 contentChild.required 函數就不會拋出錯誤。

示範 3:按類型查詢 Angular 元件

import { Component, ChangeDetectionStrategy, signal, computed } from '@angular/core';

const imgURL = 'https://picsum.photos/300/200';

@Component({
 selector: 'app-photo',
 standalone: true,
 template: `
   <div class="photo">
     <img [src]="img()" alt="Random picture" />
   </div>
 `,
})
export default class AppPhotoComponent {
 #random = signal(Date.now());

 img = computed(() => `${imgURL}?random=${this.#random()}`) 

 loadImage() {
   this.#random.set(Date.now());
 }
}

AppPhotoComponent 組件具有附加到圖像 URL 的 random seed signal。 當 loadImage 函數更新signal 值時,img computed signal 會產生一個新的圖片 URL。

import { Component, ChangeDetectionStrategy, contentChild } from '@angular/core';
import AppPhotoComponent from './photo.component';

@Component({
 selector: 'app-photo-wrapper',
 standalone: true,
 template: `
   <div class="photo-wrapper">
     <ng-content />
     <button (click)="changeImage()">Change photo</button> 
   </div>
 `,
})
export default class AppPhotoWrapperComponent {
 photo = contentChild.required(AppPhotoComponent);

 changeImage() {
   this.photo().loadImage();
 }
}

AppPhotoWrapperComponent 組件包含一個預設的 <ng-content>,我們可以投影 AppPhotoComponent 組件。此組件使用 contentChid.required 函數來查詢 AppPhotoComponent 組件。 changeImage 方法呼叫 loadImage 方法來顯示新圖片。

<app-photo-wrapper>
     <!-- <app-photo-size size /> -->
     <app-photo style="margin-bottom: 0.25rem;" />
</app-photo-wrapper>

<app-photo-wrapper> 標籤的內部,有一個 <app-photo> 標籤,並將對應的照片組件投影到<ng-content>contentChild.required 函式成功查詢 AppPhotoWrapperComponent 中的 AppPhotoComponent

結論:

  • contentChild 可以查詢元素、ngTemplates、指令 (directive) 和組件。第一個參數是一個 selector,它是範本變數 (template variables) 或類型。
  • read 屬性指定 contentChild 要傳回的元素類型
  • 如果範本中至少存在一個元素,我們可以使用 contentChild.required 函數。否則,我們應該使用 contentChild 函數,它可以傳回 signalundefined
  • contentChild 函數的 selector 與範本中的多個元素相符時,傳回第一個。
  • 如果 content.required 無法查詢某個元素,則函數會拋出錯誤。

鐵人賽的第 21 天就這樣結束了。

參考:


上一篇
Day 20 - viewChildren函數介紹
下一篇
Day 22 - contentChildren 函數介紹
系列文
Signal API in Angular36
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言