iT邦幫忙

2024 iThome 鐵人賽

DAY 25
0
JavaScript

Signal API in Angular系列 第 25

Day 25 - 使用outputFromObservable函數將Observable轉換為OutputRef

  • 分享至 

  • xImage
  •  

outputToObservable 相反的是 outputFromObservable。 此實用程式函數將 Observable 轉換為 OutputrRef,以便開發人員可以訂閱 (subscribe) 它來使用該值。與 outputToObservable 函數相同,函數位於 rxjs-interop 套件中。

我一直在努力尋找 outputFromObservable 的良好應用,直到 Chau(一位來自越南的 Angular GDE)描述了他如何使用該函數轉換 reactive form 的 valueChanges Observable 為 OutputRef。而且,outputFromObservable 還可以將 unified control state changed events 轉換為 OutputRef` 進而訂閱。

讓我們來看看第 23 天的相框演示範例。

例子 1:將 valueChanges Observable 轉換為輸出

@Component({
 selector: 'app-photo-size-output',
 standalone: true,
 imports: [ReactiveFormsModule, FormsModule],
 template: `
   <form [formGroup]="form" (ngSubmit)="formSubmitSub.next()">
     <label for="width">
       <span>Width: </span>
       <input type="number" id="width" name="width" formControlName="width" />
     </label>
     <label for="height">
       <span>Height: </span>
       <input type="number" id="height" name="height" formControlName="height" />
     </label>
     <button type="submit" [disabled]="this.form.invalid">Change photo</button>
   </form>
 `,
})
export default class AppPhotoSizeOutputComponent {

 form = new FormGroup({
   width: new FormControl(300,  { nonNullable: true, validators: [ Validators.required, Validators.min(30)] }),
   height: new FormControl(200, { nonNullable: true, validators: [ Validators.required, Validators.min(30)] }),
 });

 formSubmitSub = new Subject<void>();

 isBigPortrait = outputFromObservable(this.form.controls.width.valueChanges
   .pipe(
     debounceTime(300),
     map((v) => v >= 700),
   ));
}

AppPhotoSizeOutputComponent 建立一個 reactive form 來輸入照片的 width 和 height。 我想當 width FormControl 的值至少為 700 時訂閱 OutputRef

isBigPortrait = outputFromObservable(this.form.controls.width.valueChanges
   .pipe(
     debounceTime(300),
     map((v) => v >= 700),
   ));

isBigPortrait 是一個 OutputRef,當寬度至少為 700 時發出 true,否則發出 false。

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

@Component({
 selector: 'app-photo-wrapper-output',
 standalone: true,
 imports: [AppPhotoSizeOutputComponent, AppPhotoOutputComponent, FormsModule],
 template: `
   <div class="photo-output-wrapper">
     <app-photo-size-output #size />
     @if(showName()) {
       <div>
         Name: <input [(ngModel)]="name" />
       </div>
     }
   </div>
 `,
})
export default class AppPhotoWrapperOutputComponent {
 size = viewChild.required(AppPhotoSizeOutputComponent);
 showName = signal(false);
 name = signal('Luke');

 constructor() {
   effect(() => {
     this.size().isBigPortrait.subscribe((value) => this.showName.set(value));
   });
 }
}

AppPhotoWrapperOutputComponent 組件匯入 AppPhotoSizeOutputComponent 組件以顯示 reactive form。 viewChild 函數查詢 AppPhotoSizeOutputComponent 組件以存取 isBigPortrait output。

constructor() {
   effect(() => {
     this.size().isBigPortrait.subscribe((value) => this.showName.set(value));
   });
}

在組件的 effect 中,訂閱了 isBigPortrait 來設定 showName signal 的值。

@if(showName()) {
   <div>Name: <input [(ngModel)]="name" /></div>
}  

showName 訊號有條件地在 HTML 範本中顯示輸入欄位,以供使用者輸入名稱並更新 name 訊號。

例子 2:將 FormSubmittedEvent 轉換為 output

@Component({
 selector: 'app-photo-size-output',
 standalone: true,
 imports: [ReactiveFormsModule, FormsModule],
 template: `
   <form [formGroup]="form" (ngSubmit)="formSubmitSub.next()">
     … width and height form controls …
     <button type="submit" [disabled]="this.form.invalid">Change photo</button>
   </form>
 `,
})
export default class AppPhotoSizeOutputComponent {
 formSubmitSub = new Subject<void>();

 submittedValues = outputFromObservable(this.form.events.pipe(
   filter((e) => e instanceof FormSubmittedEvent),
   filter((e) => e.source.valid),
   map(({ source }) => ({
     timestamp: new Date().toISOString(),
     value: source.value as { width: number; height: number }
   })),
 ));
}

表單事件過濾 FormSubscribedEven 以驗證事件來源。當表單資料有效時,map 運算子將 Observable 對應到目前時間和資料。此函數將新的 Observable 轉換為 output 並將其指派給 submittedValues 變數。

@Component({
 selector: 'app-photo-wrapper-output',
 standalone: true,
 imports: [AppPhotoSizeOutputComponent, AppPhotoOutputComponent, FormsModule],
 template: `
   <div class="photo-output-wrapper">
     <p>{{ text() }}</p>
     @if (this.formObj().timestamp) {
       <p>Form submitted at {{ this.formObj().timestamp }}.</p>
     }
     <app-photo-size-output />
     <app-photo-output [img]="this.formObj().url" />
   </div>
 `,
})
export default class AppPhotoWrapperOutputComponent {
 formObj = signal({
   url: 'https://picsum.photos/300/200',
   timestamp: ''
 })

 size = viewChild.required(AppPhotoSizeOutputComponent);

 text = computed(() => {
   const isShowName = this.showName();
   const requester = isShowName ? `${ this.name() } wants ` : '';
   return `${requester}${ this.formObj().url }`;
 });

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

AppPhotoWrapperOutputComponent 組件的 effect 中,訂閱 submittedValues OutputRef來提取時間、寬度和高度來建立圖像URL。 圖像 URL 和時間會覆蓋 formObj signal 以更新 HTML 範本。

<div class="photo-output-wrapper">
     <p>{{ text() }}</p>
     @if (this.formObj().timestamp) {
       <p>Form submitted at {{ this.formObj().timestamp }}.</p>
     }
     <app-photo-size-output />
     <app-photo-output [img]="this.formObj().url" />
</div>
text = computed(() => {
   const isShowName = this.showName();
   const requester = isShowName ? `${ this.name() } wants ` : '';
   return `${requester}${ this.formObj().url }`;
 });

此範本在第一個段落元素中顯示名稱和圖像 url。當表單提交的時間不是空字串時,第二個段落元素顯示 ISO 日期字串。 該 url 也是 AppPhotoOutputComponent 組件的 input,用於顯示新影像。

結論:

  • outputFromObservableObservable 轉換為 OutputRef,以便可以訂閱 (subscribe) 該值。
  • outputFromObservable 駐留在 rxjs-interop 套件中,就像 toSignaltoObservable 一樣
  • 當我們使用 outputFromObservable 函數建立 OutputRef 時,可以透過程式訂閱它。

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

參考:


上一篇
Day 24 - 使用 outputToObservable 函數將 OutputEmitterRef 轉換為Observable
下一篇
Day 26 - 將 Decorators遷移到 input、queries 和 output 函數
系列文
Signal API in Angular36
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言