iT邦幫忙

2024 iThome 鐵人賽

DAY 16
0
JavaScript

Signal API in Angular系列 第 16

Day 16 - 組件與model input之間的溝通

  • 分享至 

  • xImage
  •  

在現代 Angular 中,組件中的雙向資料綁定 (two-way data binding),一般經驗法則是採用單向資料流 (unidirectional data flow) 並實作自訂 event emitter 以將變更傳播到父組件。更流行的解決方案是使用 SubjectBehaviorSubject 在父組件和子組件之間分享資料。透過 Signal APIModel input,雙向資料綁定 (Two-way data binding) 的複雜性顯著降低。

我用來在父組件和子組件之間進行通訊的一些模式。

  • 使用 inputevent emitter。 input 名為 x,event emitter名為 xChange
  • Subject as a service。 組件使用 Subject 或 BehaviorSubject 來分享資料。
  • 使用 model input 將資料雙向綁定 (two-way data binding) 到組件層級的 plain valuesignal

今天,我想介紹一下signal 方式的雙向綁定 (two-way data binding),即在組件中使用 model input

必需和可選的 Model Input

  • model.required() 表示組件有指定輸入值。如果組件找不到輸入值,則會拋出錯誤。
  • model() 表示當組件沒有指定輸入值時使用初始值。

Model input與 Signal input 的區別

  • Model input 允許讀寫操作,而 signal input 是唯讀的。
  • Model input 沒有 transform 屬性;因此,值不能透過變換函數進行變換。
  • Model input 傳回 ModelSignal,而 signal input 傳回 Signal。

在這篇文章中,我想展示在組件之間共享資料的舊方法。然後,我將展示 model inputsignal 方式。 model input 將值綁定到 signalplain variable, 以在組件之間傳達資料變更。

舊方法 1:使用 input 和 event emitter

import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from "@angular/core";
import { FormsModule } from "@angular/forms";

@Component({
 selector: 'app-sizer',
 standalone: true,
 imports: [FormsModule],
 template: `
   <p>Default range slider:</p>
   <div>
     <input type="range" min="100" max="300"
       [ngModel]="size" (ngModelChange)="resize($event)" />
   </div>
 `,
 styles: ``,
 changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppSizerComponent {
 @Input() size!: number | string;
 @Output() sizeChange = new EventEmitter<number>();
  
 resize(delta: number) {
   this.size = delta;
   this.sizeChange.emit(this.size);
 }
}

AppSizerComponent 組件由一個 slider 組成,該組件將選定的值傳送到父組件。 AppSizerComponent 有一個 size 輸入和一個 sizeChange event emitter。

@Component({
 selector: 'app-root',
 standalone: true,
 imports: [AppSizerComponent, AppSquareComponent],
 template: `
   <h3>2-way binding with Input and Event Emitter</h3>
   <app-sizer [(size)]="value" />
   <app-square [value]="value" />
 `,
})
export class App {
 value = 120;
}

AppSizerComponent 執行雙向綁定 (two-way data biding),將 value 變數綁定到 size 以獲得最新值。然後,AppSquareComponent 組件會取得輸入值以顯示方塊。

@Component({
 selector: 'app-square',
 standalone: true,
 template: `
   <p>Size: {{ value() }}</p>
   <div class="square" [style.width.px]="value()" [style.height.px]="value()">{{ value() }}</div>
 `,
})
export class AppSquareComponent { 
 value = input(0);
}

舊方法2:使用 Subject as a Service 作為資料分享

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class AppSizerService {
 private valueSub = new BehaviorSubject(120);
 value$ = this.valueSub.asObservable();

 update(newValue: number) {
   this.valueSub.next(newValue);
 }

 get() {
   return this.valueSub.getValue();
 }
}

AppSizerService service 有一個 BehaviorSubject 來儲存 slider 的目前值。 asObservable 將 Observable 分配給 value$,並且當值變更時訂閱它的其他組件會收到通知。

import { ChangeDetectionStrategy, Component, inject } from "@angular/core";
import { AppSizerService } from "./app-sizer.service";
import { FormsModule } from "@angular/forms";

@Component({
 selector: 'app-sizer-subject',
 standalone: true,
 imports: [FormsModule],
 template: `
   <p>Default range slider:</p>
   <div>
     <input type="range" min="100" max="300" [ngModel]="service.get()" (ngModelChange)="service.update($event)">
   </div>
 `,
})
export class AppSizerSubjectComponent {
 service = inject(AppSizerService);
}

AppSizerSubjectComponent 組件注入 AppSizerService service。 NgModel input 綁定到 service 的 get 方法以顯示目前值。 NgModelChange event emitter將新值寫入 BehaviorSubject

<app-sizer-subject />
<app-square [value]="(value$ | async) ?? 0" />
value$ = inject(AppSizerService).value$

在 App 組件中,它使用 async pipe 來解析 value$ Observable 並將該值傳遞給 AppSquareComponent input 以顯示正方形。

Signal 方式:使用 model input 在組件中進行雙向資料綁定 (two-way data binding)

import { ChangeDetectionStrategy, Component, model } from "@angular/core";
import { FormsModule } from '@angular/forms';

@Component({
 selector: 'app-sizer-model-input-required',
 standalone: true,
 imports: [FormsModule],
 template: `
   <p>Default range slider:</p>
   <div>
     <input type="range" min="100" max="300" [(ngModel)]="size" />
   </div>
 `,
})
export class AppSizerModelInputRequiredComponent { 
 size = model.required({ alias: 'size2' });
}

AppSizerModelInputRequiredComponet 組件具有別名 size2 的 必需的 model input。輸入欄位的 NgModelsize2 model input 雙向綁定 (two-way data binding)。

<h3>Use required model input</h3>
<app-sizer-model-input-required [(size2)]="plain" />
<app-square [value]="plain" />

plain = 120;

組件的 size2 雙向綁定 (two-way data binding) 到 plain 變數。 然後,將 plain 變數傳遞給 AppSquareComponent 以顯示方塊。

<app-sizer-model-input-required [(size2)]="s" />
<app-square [value]="s()" />

s = signal(120);

組件的 size2s signal 雙向綁定 (two-way data binding)。

<h3>Use model input</h3>
<div>
   <input type="range" min="100" max="400" [(ngModel)]="a" />
</div>
<app-square [value]="a()" />

a = model(250);

在此例子中,model input的初始值為 250。 Range input 將 NgModel 綁定到 model input 並顯示 250。當 range slider 移動時, model input 將設定為新值。 最後, AppSquareComponent 顯示一個具有新長度的方塊。

結論:

  • Two-way data binding的傳統方式是使用inputevent emitterinput的名稱為 x,event emitter的名稱為 xChange。
  • 然後,發明 subject as a service。 一個組件向 BehaviorSubject 發出一個值,另一個組件使用Observable 並相應地更新其狀態或HTML 範本。
  • Model input透過將signalplain value綁定到signal來簡化two-way data binding
  • Model input允許讀寫操作,而signal input是唯讀的。與signal input類似,model input可以是必需的、可選的並且具有別名。

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

參考:


上一篇
Day 15 - 在 Directive Composition API 中使用 Signal 和 Signal Input
下一篇
Day 17 - viewChild 函數簡介
系列文
Signal API in Angular36
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言