Angular Forms - Reactive Forms and Template-Driven Forms
寫網頁的過程中免不了一定會出現「表單」,Angular 提供兩種不同的表單建立方式來處理使用者輸入,分別是 reactive forms 和 template-driven forms。
兩種方式都能夠
- 抓取使用者在 view 上的 input events
- 驗證使用者輸數
- 建立 form model 和 data model 給後續表單資料更新
- 建議一套追蹤表單資料變化的機制
而本文將介紹兩種方式的異同和使用方式。
Choosing an approach
兩種方式的關鍵差異可由下張表來描述
REACTIVE | TEMPLATE-DRIVEN | |
---|---|---|
Setup of form model | Explicit, created in component class | Implicit, created by directives |
Data model | Structured and immutable | Unstructured and mutable |
Data flow | Synchronous | Asynchronous |
Form validation | Functions | Directives |
因為 reactive form 提供直接對接底層的 form API,且 view 和 data model 的更新為同步的,測試時較不須要處理 change detection 問題,因此當我們要建立 large-scale 或複雜的表單時,使用上會比 templare-driven form 來得方便。
Setting up the form model
Reactive and template-driven forms 共用 building blocks 來追蹤使用者與 form input 元素的互動以及 component 裡的 form data 變化。兩種方式只差別在開發者如何建立和管理 form-control instances。
Common form foundation classes
FormControl
tracks the value and validation status of an individual form control.FormGroup
tracks the same values and status for a collection of form controls.FormArray
tracks the same values and status for an array of form controls.ControlValueAccessor
creates a bridge between Angular FormControl instances and built-in DOM elements.
Setup in reactive forms
(1) Register the reactive forms module
1import { Component } from '@angular/core';
2import { FormControl } from '@angular/forms';
3
4@Component({
5 selector: 'app-reactive-favorite-color',
6 template: `
7 <!-- (3) Register the control in the template -->
8 Favorite Color: <input type="text" [formControl]="favoriteColorControl">
9 `
10})
11export class FavoriteColorComponent {
12 favoriteColorControl = new FormControl(''); // (2) Generate a new FormControl
13}
更多常見的使用:
- 使用
FormGroup
建立巢狀表單欄位 - 使用
setValue()
方法更改單一FormControl
的值 - 使用
patchValue()
方法一次更新多個FormControl
的值 - 使用
FormBuilder
service 使建立表單更容易,不必自行重複處理 form group 和 form control - 使用
FormArray
動態建立長度不固定的 unamedFormControl
在 reactive form 中,form model(FormControl
instance)是真相來源。它透過 [formControl]
directive 與 template input element 綁定,提供表單元素的值和狀態。
Setup in template-driven forms
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-template-favorite-color',
5 template: `
6 Favorite Color: <input type="text" [(ngModel)]="favoriteColor">
7 `
8})
9export class FavoriteColorComponent {
10 favoriteColor = '';
11}
在 template-driven form 中,form model 是隱含的,是 direcive NgModel
為 input element 建立並管理了 FormControl
instance。
在此方式中,template 是真相來源,開發者沒有辦法直接修改 FormControl
instance。
Data flow in forms
Reactive 和 template-driven forms 處理 view 和 component model 資料更新的流程不同。
Data flow in reactive forms
在 reactive forms 中,view 中的表單元素都直接與 form model 相連(a FormControl
instance)。
從 view 到 model 以及從 model 到 view 的更新是同步的,而且不依賴於 UI 是如何渲染的。
How the data flows when an input field's is changed from the view:
- The user types a value into the input element, in this case the favorite color Blue.
- The form input element emits an "input" event with the latest value.
- The control value accessor listening for events on the form input element immediately relays the new value to the
FormControl
instance. - The
FormControl
instance emits the new value through thevalueChanges
observable. - Any subscribers to the
valueChanges
observable receive the new value.
How the data flows when a programmatic change to the model is propagated to the view:
- The user calls the
favoriteColorControl.setValue()
method, which updates theFormControl
value. - The
FormControl
instance emits the new value through thevalueChanges
observable. - Any subscribers to the
valueChanges
observable receive the new value. - The control value accessor on the form input element updates the element with the new value.
Data flow in template-driven forms
How data flows when an input field's value is changed from the view:
- The user types Blue into the input element.
- The input element emits an "input" event with the value Blue.
- The control value accessor attached to the input triggers the
setValue()
method on theFormControl
instance. - The
FormControl
instance emits the new value through thevalueChanges
observable. - Any subscribers to the
valueChanges
observable receive the new value. - The control value accessor also calls the
NgModel.viewToModelUpdate()
method which emits anngModelChange
event. - Because the component template uses two-way data binding for the
favoriteColor
property, thefavoriteColor
property in the component is updated to the value emitted by thengModelChange
event (Blue).
How data flows from model to view when the favoriteColor
changes from Blue to Red:
- The favoriteColor value is updated in the component.
- Change detection begins.
- During change detection, the
ngOnChanges
lifecycle hook is called on theNgModel
directive instance because the value of one of its inputs has changed. - The
ngOnChanges()
method queues an async task to set the value for the internalFormControl
instance. - Change detection completes.
- On the next tick, the task to set the
FormControl
instance value is executed. - The
FormControl
instance emits the latest value through thevalueChanges
observable. - Any subscribers to the
valueChanges
observable receive the new value. - The control value accessor updates the form input element in the view with the latest
favoriteColor
value.
Mutability of the data model
- Reactive forms 中的資料是 immutable 的,每次 data model 有所改變,
FormControl
instance 都會回傳一個新的 data model 而不是更新現有的。這也讓 change detection 較有效率。 - Template-driven forms 則基於 two-way data binding 在 template 有任何改變時去更新 component data model。