深入淺出 Angular 的 Two-Way Binding
Interpolation binding 與 property binding 都當 class 的 field 有變動時,會自動反應到 class 的 field,若 HTML 有任何變動也能反應到 class 的 field,這就是 two-way binding 了。
Version
Angular 4.3
One-Way Binding
在 如何使用 Angular 實作下拉選單? 一文中,我們使用了 HTML template reference varible 方式,也使用 DOM event 方式,無論哪種方法,使用的是 one-way binding 的技術。
- Interpolation Binding:當 class 的 field 資料改變,HTML 會自動改變
- Event Binding:當 HTML 資料改變,發動 event,由 class 的 method 去修改 field
Two-Way Binding
- Two Way Binding:資料不用透過 method 修改,當 HTML 改變時,field 會跟著改變;且當 field 改變時,HTML 也會隨之改變。
土炮 Two-Way Binding
Two-way binding 理念很棒,利用既有的 property binding、event binding、interpolation binding,該如何寫出 two-way binding 呢?
src/app/app.component.html
1 | <select id="TDDSelect" [value]="selectedId" (change)="selectedId = $event.target.value"> |
1 | [value]="selectedId" |
使用 property binding 將 value
直接綁定到 selectedId
field,也就是當 selectedId
field 有任何修改,都會反應到 selectedId
的 value
。
1 | (change)="selectedId = $event.target.value" |
使用 event binding 將 change
event 直接綁定到雙引號內的 statement,也就是將 $event.target.value
直接指定給 selectedId
field。
使用 interpolation binding 當 selectedId
field 有任何變動,HTML 會自動跟著變化。
透過 property binding 與 event binding 的組合,當 HTML 改變時,field 會跟著改變;且當 field 改變時,HTML 也會隨之改變。
src/app/app.component.ts
1 | import { Component } from '@angular/core'; |
Class 部份完全不用任何 method 幫忙,只剩下 field 部分。
這樣就完成了我們土炮的 two-way binding 了。
使用 ngModel
雖然土炮的方式可行,Angular 為了讓我們更方便,特別設計了 ngModel
directive,讓我們使用更簡單的語法就能完成 two-way binding。
src/app/app.module.ts
1 | import { BrowserModule } from '@angular/platform-browser'; |
12 行
1 | imports: [ |
ngModel
directive 需要使用 FormsModule
,而 Angular CLI 預設沒有載入,必須手動加上。
src/app/app.component.html
1 | <select id="TDDSelect" [ngModel]="selectedId" (ngModelChange)="selectedId = $event"> |
[ngModel]="selectedId"
:使用 property binding,將selectedId
field 綁定到ngModel
directive(ngModelChange)="selectedId = $event"
:使用 event binding,當ngModelChange
event 時,執行selectedId = $event
由 value
attribute 換 ngModel
directive 較無感,但由 input
event 換 ngModelChange
event 就比較有感了,因為 $event.target.value
換成 $event
更為精簡。
這也意味著 Angular 內部會自己將處理
$event.target.value
。
若仔細去看,會發現 (ngModelChange)
與 $event
是贅字,因此可以再次簡化。
src/app/app.component.html
1 | <select id="TDDSelect" [(ngModel)]="selectedId"> |
(ngModelChange)
與 $event
再次被簡化,將 ()
與 []
合一,就變成了 [(ngModel)]="selectedId"
了。
[()]
語法稱為 Banana in a Box,將 property binding 與 event binding 合而為一。
Recap
- 原本的
value
attribute 抽象化成ngModel
directive。 - 原本的
change
event 抽象化成ngModelChange
event,change
event 會觸發ngModelChange
event - 原本的
$event.target.value
簡化成$event
,Angular 會在內部處理 ()
與[]
合一成為[()]
ngModel 內部運行機制
- 在 HTML 修改觸發 DOM 的
change
event change
event 觸發了 Angular 的ngModelChange
event- 將
$event
傳給selectedId
field ControlValueAccessor
將$event.target.value
指定給selectedId
fieldselectedId
field 被修改,引發 interpolation binding 自動更新 HTML
步驟 4 就是將
$event.target.value
轉成 field 的黑魔法所在
是否該使用 ngModel?
Two-way binding 是 AngularJS 的招牌菜,但因為底層使用 dirty check 方式,只要頁面 two-way binding 用得過多,效率就會明顯變慢,因此 ReactJS 改用 one-way binding ,在效能上完全打趴 AngularJS。
在 Angular 2 一開始時,由於 AngularJS 的經驗與 ReatJS的影響,的確聽到不少反對再用 Two-way binding 的聲音,不過深入了解之後,會發現在 Angular 的[(ngModel)]
雖然表面上看起來是 two-way binding,但其實內部用的依然是 one-way binding,也就是 [(ngModel)]
只是個 syntax sugar 而已,因此可以放心大膽使用 [(ngModel)]
,不用再擔心效能問題。
Conclusion
ngModel
在實務上非常好用,語法也很精簡,但 Banana in a Box 語法很特殊,一般人都是背下來,經由本文的推導,應該更能體會[()]
語法的由來。ngModel
雖然表面上看起來是 two-way binding,但底層用的仍然是 one-way binding,因此沒有效能上的問題,請放心使用。
Reference
Angular, Two-way binding
Angular, NgModel
Throughtram, Two-Way Data Binding in Angular
CK’s Notepad, [Angular] Two-way Binding 的運作方式