在現代 Angular 中,組件中的雙向資料綁定 (two-way data binding),一般經驗法則是採用單向資料流 (unidirectional data flow) 並實作自訂 event emitter 以將變更傳播到父組件。更流行的解決方案是使用 Subject 或BehaviorSubject 在父組件和子組件之間分享資料。透過 Signal API 和 Model input,雙向資料綁定 (Two-way data binding) 的複雜性顯著降低。
input 和 event emitter。 input 名為 x,event emitter名為 xChange。Subject 或 BehaviorSubject 來分享資料。model input 將資料雙向綁定 (two-way data binding) 到組件層級的 plain value 或 signal。今天,我想介紹一下signal 方式的雙向綁定 (two-way data binding),即在組件中使用 model input。
model.required() 表示組件有指定輸入值。如果組件找不到輸入值,則會拋出錯誤。model() 表示當組件沒有指定輸入值時使用初始值。Model input 允許讀寫操作,而 signal input 是唯讀的。Model input 沒有 transform 屬性;因此,值不能透過變換函數進行變換。Model input 傳回 ModelSignal,而 signal input 傳回 Signal。在這篇文章中,我想展示在組件之間共享資料的舊方法。然後,我將展示 model input 的 signal 方式。 model input 將值綁定到 signal 和 plain variable, 以在組件之間傳達資料變更。
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);
}
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 以顯示正方形。
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。輸入欄位的 NgModel 與 size2 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);
組件的 size2 與 s 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的傳統方式是使用input和event emitter。 input的名稱為 x,event emitter的名稱為 xChange。subject as a service。 一個組件向 BehaviorSubject 發出一個值,另一個組件使用Observable 並相應地更新其狀態或HTML 範本。Model input透過將signal或plain value綁定到signal來簡化two-way data binding。Model input允許讀寫操作,而signal input是唯讀的。與signal input類似,model input可以是必需的、可選的並且具有別名。鐵人賽的第 16 天到此結束。