iT邦幫忙

2024 iThome 鐵人賽

DAY 23
0
JavaScript

Signal API in Angular系列 第 23

Day 23 - output 函數介紹

  • 分享至 

  • xImage
  •  

今天,我將介紹與 Output 裝飾器 (decorator) 相對應的 output 函數。 output 函數負責向父組件發送值。output 函數傳回一個 OutputEmitterRef,它有兩個方法:emitsubscribe。儘管 OutputEmitterRefsubscribe 方法來使用發出的值,但它與 RxJS 無關。與 @Output 裝飾器 (decorator) 相同, output 函數也有一個別名 (alias) 屬性,可以重新命名自訂事件。

在以下例子中,我展示了 output 如何向父組件傳送值,以及消費者如何使用 subscribe 方法使用傳送的值。

例子 1:將值輸出到父組件

import { Component, ChangeDetectionStrategy, signal, output } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
 selector: 'app-photo-size-output',
 standalone: true,
 imports: [FormsModule],
 template: `
   <div style="margin-bottom: 1rem;">
     Width: <input type="number" [(ngModel)]="width" />
     Height: <input type="number" [(ngModel)]="height" />
     <button (click)="size.emit({ width: width(), height: height() })">Change photo</button>
   </div>
 `,
})
export default class AppPhotoSizeOutputComponent {
 width = signal(300);
 height = signal(200);

 size = output<{
   width: number,
   height: number,
 }>({ alias: 'dimensions'});
}

AppPhotoSizeOutputComponentwidthheight signals 綁定到 template-driven 表單。當使用者點擊按鈕時,點擊事件會將 widthheight 物件傳送到 size output。 size output 的別名 dimensions;因此,自訂事件名稱被重新命名為 dimensions

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

@Component({
 selector: 'app-photo-output',
 standalone: true,
 template: `
   <div class="photo">
     <img [src]="img()" alt="Random picture" />
   </div>
 `,
})
export default class AppPhotoOutputComponent {
 img = input.required<string>();
}

AppPhotoOutputComponent 組件具有必需的 img signal input,用於將圖像 URL 綁定到圖像元素。

import { Component, ChangeDetectionStrategy, signal, output } from '@angular/core';
import AppPhotoOutputComponent from './photo-output.component';
import AppPhotoSizeOutputComponent from './photo-output-size-output.component';

@Component({
 selector: 'app-photo-wrapper-output',
 standalone: true,
 imports: [AppPhotoSizeOutputComponent, AppPhotoOutputComponent],
 template: `
   <div class="photo-output-wrapper">
     <app-photo-size-output (dimensions)="updateSize($event)" />
     <app-photo-output [img]="img()" />
   </div>
 `,
})
export default class AppPhotoWrapperOutputComponent {
 img = signal('https://picsum.photos/300/200');
 imgUrl = output<string>();

 updateSize(result: { width: number; height: number }) {
   const { width, height } = result;
   const url = `https://picsum.photos/${width}/${height}?random=${Date.now()}`;
   this.img.set(url);
   this.imgUrl.emit(url);
 }
}

AppPhotoWrapperOutputComponent 組件是 AppPhotoSizeOutputComponentAppPhotoOutputComponent 組件的容器。 AppPhotoSizeOutputComponent 組件的自訂 size 事件將值傳送到父組件。 updateSize 方法使用結果建構影像 URL,以 URL 覆寫 img signal,並將其傳送到 App 組件。 AppPhotoOutputComponent 所需的輸入綁定到 img signal 以顯示新圖片。

@Component({
 selector: 'app-root',
 standalone: true,
 imports: [AppPhotoWrapperOutputComponent],
 template: `
   <p>{{ imgUrl() }}</p>
   <app-photo-wrapper-output (imgUrl)="imgUrl.set($event)" />
 `,
})
export class App {
 imgUrl = signal('');
}

AppPhotoWrapperOutputComponent 組件將 imageUrl 傳送到 App 組件,並顯示該值。

例子 2:以 subscribe 訂閱 output 值

import { Component, signal, output } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
 selector: 'app-photo-size',
 standalone: true,
 imports: [FormsModule],
 template: `
   <div style="margin-bottom: 1rem;">
     Width: <input type="number" [(ngModel)]="width" />
     Height: <input type="number" [(ngModel)]="height" />
     <button (click)="size.emit({ width: width(), height: height() })">Change photo</button>
   </div>
 `,
})
export default class AppPhotoSizeComponent {
 width = signal(300);
 height = signal(200);

 size = output<{
   width: number,
   height: number,
 }>();
}

AppPhotoSizeComponent 具有綁定到範本驅動表單 (template-driven form) 的 widthheight signals。當使用者點擊按鈕時,點擊事件會將 widthheight 物件傳送到 size output。

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

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

AppPhotoComponent 組件有一個 img signal,用於將圖像 URL 綁定到圖像元素。

import { Component, ChangeDetectionStrategy, contentChild, effect } from '@angular/core';
import AppPhotoComponent from './photo.component';
import AppPhotoSizeComponent from './photo-size.componen';

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

 constructor() {
   effect(() => {
     this.photoSize().size.subscribe(({ width, height }) => {
       const url = `https://picsum.photos/${width}/${height}?random=${Date.now()}`;
       this.photo().img.set(url);       
     });
   });
 }
}

AppPhotoWrapperComponent 組件使用 contentChild 函數來查詢 AppPhotoComponentAppPhotoSizeComponent 組件。 AppPhotoWrapperComponent 組件訂閱 AppPhotoSizeComponent 組件的 size output。當 size output 發出父級的值時, subscribe 方法會建構圖片 URL 並設定 AppPhotoComponent 組件的 img signal。signal 接收新值,AppPhotoComponent 組件顯示新圖片。

<h3>Subscribe output function</h3>
<app-photo-wrapper>
   <app-photo-size />
   <app-photo style="margin-bottom: 1rem;" />
</app-photo-wrapper>

App 組件中,AppPhotoSizeComponentAppPhotoComponent 組件被投影到 AppPhotoWrapperComponent,以便 contentChild 函數可以查詢它們,並且可以透過 subscribe 訂閱 the size output。

結論:

  • output 函數允許元件向父組件發出值。
  • output 函數傳回具有 emitsubscribe 方法的 OutputEmitterRefemit 方法將值傳送給父元件。 subscribe 方法以程式設計方式使用發出的值。
  • output 函數接受帶有別名屬性的選項物件。內部 output 名稱可以與組件層級的事件名稱不同。
  • outputToObservable 將輸出轉換為 ObservableoutputFromObservableObservable 轉換為輸出。 這些功能可以在 rxjs-interop 套件中找到。

鐵人賽的第 23 天到此結束。

參考:


上一篇
Day 22 - contentChildren 函數介紹
下一篇
Day 24 - 使用 outputToObservable 函數將 OutputEmitterRef 轉換為Observable
系列文
Signal API in Angular36
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言