Angular支援template-drive form和reactive form,但是,signal容易與template-driven form配合使用。在NgModel能夠與signal執行two-way binding之後,我更多地使用template-driven form。
今天,我想講一下NgModel的故事。 Angular團隊最初引入它是為了綁定到primitve或物件屬性 (object property)。現在,ngModel可以綁定到signals來讀取和寫回它們。
NgModel可透過將FormsModule匯入獨立元件 (standalone component) 來使用。
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-root',
standalone: true,
imports: [FormsModule],
template: ‘’,
})
export class App {}
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-root',
standalone: true,
imports: [FormsModule],
template: `
<div>
<p class="bold">NgModel a primitive</p>
<p>
<span>Num:</span><input type="number" [(ngModel)]="num" />
</p>
<p>
<span>Num Property:</span><input type="number" [(ngModel)]="obj.num" />
</p>
<span>Output: </span><span>{{ num }}, {{ num + 2 }}, {{ obj.num }}, {{ obj.num + 2 }}</span>
<hr />
</div>
`,
})
export class App {
num = 1;
obj = { num: 1 };
}
當我在2016年開始使用Angular 2時,開發人員只能分別將primitive和物件屬性 (object property)綁定到ngModel。在範例中,數字和數字類型的物件屬性是與ngModel的雙向綁定。 對於剛接觸Angular的人來說,[(ngModel]]="num"是雙向綁定 (two-way binding)ngModel的語法。當輸入欄位發生變化時,對應的變數也會改變。範本顯示最新值:num,num + 2,obj.num 和obj.num + 2。
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { BehaviorSubject, map } from 'rxjs';
@Component({
selector: 'app-root',
standalone: true,
imports: [FormsModule, AsyncPipe],
template: `
<div>
<p class="bold">NgModel a Behavior Subject</p>
<p>
<span>Num:</span><input type="number" [ngModel]="numSub.getValue()" (ngModelChange)="numSub.next($event)" />
</p>
<span>Output: </span><span>{{ numSub.getValue() }}, {{ plus2$ | async }}</span>
<hr />
</div>
`,
})
export class App {
// bind a behaviorSubject to ngModel
numSub = new BehaviorSubject(1);
// create a new Observable that adds 2 to the new num
plus2$ = this.numSub.pipe(map((x) => x + 2));
}
一段時間後,開發了一種反應式模式 (reactive pattern) 來將BehaviorSubject綁定到ngModel。當BehaviorSubject獲得新的表單值時,訂閱該subject的消費者將獲取該值並可以操縱它來派生新結果。 在範例中,輸入欄位顯示 1,因為numSub的初始值是透過[ngModel]=numSub傳遞的。當輸入欄位更新時, (ngModelChange)=numSub.next($even)會將數值傳送給subject。 plus2$ Observable然後將值對應到value + 2。 最後,該組件匯入AsyncPipe並解析 (resolve) 範本中的plus2$。
在Angular引入signal之後,我用signal和computed signal重寫了BehaviorSubject範例。 正是因為
RxJS最適合非同步活動和串流Signa最適合同步活動,更新輸入欄位是同步操作。signal的程式碼更改:
之前使用 RxJS
numSub = new BehaviorSubject(1);
plus2$ = this.numSub.pipe(map((v) => v + 2));
有signal後
// bind a signal to ngModel
numBeforeV17 = signal(1);
// derive a new read-only signal to show num + 2
plus2BeforeV17 = computed(() => this.numBeforeV17() + 2);
之前使用 RxJS
<p>
<span>Num:</span><input type="number" [ngModel]="numSub.getValue()" (ngModelChange)="numSub.next($event)" />
</p>
<span>Output: </span><span>{{ numSub.getValue() }}, {{ plus2$ | async }}</span>
有signal後
<p>
<span>Num:</span><input type="number" [ngModel]="numBeforeV17()" (ngModelChange)="numBeforeV17.set($event)" />
</p>
<span>Output: </span><span>{{ numBeforeV17() }}, {{ plus2BeforeV17() }}</span>
此外,我可以從遷移過程中刪除AsynPipe。 signal版本看起來與RxJS版本類似,更改的動機可能不高。 我希望您在看到NgModel與signal的雙向綁定 (two-way binding) 後改變主意。
在Angular 17.0.1中,ngModel終於支援signal了。 [(ngModel)]="signal name"是可行的,並且減少了範本中的樣板程式碼
Angular版本17.0.1之前
// bind a signal to ngModel
numBeforeV17 = signal(1);
// derive a new read-only signal to show num + 2
plus2BeforeV17 = computed(() => this.numBeforeV17() + 2);
Angular版本17.0.1之後
// bind a signal to ngModel
numV17 = signal(1);
// derive a new read-only signal to show num + 2
plus2V17 = computed(() => this.numV17() + 2);
Angular版本17.0.1之前
<p>
<span>Num:</span><input type="number" [ngModel]="numBeforeV17()" (ngModelChange)="numBeforeV17.set($event)" />
</p>
<span>Output: </span><span>{{ numBeforeV17() }}, {{ plus2BeforeV17() }}</span>
Angular版本17.0.1之後
<p>
<span>Num:</span><input type="number" [(ngModel)]="numV17" />
</p>
<span>Output: </span><span>{{ numV17() }}, {{ plus2V17() }}</span>
程式碼變更產生相同的結果,並且減少了ngModel的樣板程式碼。對我來說,我更喜歡將signal綁定到 ngModel,它會在更改時通知其他signals和computed signals。
NgModel促進signal與template-driven form的整合。NgModel的不同技術
[(ngModel)]語法將primitive或Object property綁定到表單控制元素。BehaviorSubject來追蹤HTML輸入值。 [ngModel]綁定到behaviorSubject.getValue(),並且(ngModelChange)將值傳送至behaviorSubject.next()。signal分別綁定到[ngMode]和(ngModelChange)。signal可以按以下語法綁定到ngModel:[(ngModel)]="<signal name>"。鐵人三賽第9天到此結束。
Stackblitz Demo:
最後 Signal TwoWay Binding 範例:
input type="number" [(ngModel)]="numV17()"
應該是
input type="number" [(ngModel)]="numV17"
才能正確綁定.
p.s: 其實 stackblitz Demo 都有正確範例 , 文章剛好看到就指出來一下 哈哈
Thank you. Copy and paste error.
謝謝。 複製貼上的錯誤。