用表單處理用戶輸入是許多常見應(yīng)用的基礎(chǔ)功能。 應(yīng)用通過表單來讓用戶登錄、修改個人檔案、輸入敏感信息以及執(zhí)行各種數(shù)據(jù)輸入任務(wù)。
Angular 提供了兩種不同的方法來通過表單處理用戶輸入:響應(yīng)式表單和模板驅(qū)動表單。 兩者都從視圖中捕獲用戶輸入事件、驗(yàn)證用戶輸入、創(chuàng)建表單模型、修改數(shù)據(jù)模型,并提供跟蹤這些更改的途徑。
本指南提供的信息可以幫你確定哪種方式最適合你的情況。它介紹了這兩種方法所用的公共構(gòu)造塊,還總結(jié)了兩種方式之間的關(guān)鍵區(qū)別,并在建立、數(shù)據(jù)流和測試等不同的情境下展示了這些差異。
要想學(xué)習(xí)使用表單,你應(yīng)該對這些內(nèi)容有基本的了解:
響應(yīng)式表單和模板驅(qū)動表單以不同的方式處理和管理表單數(shù)據(jù)。每種方法都有各自的優(yōu)點(diǎn)。
下表總結(jié)了響應(yīng)式表單和模板驅(qū)動表單之間的一些關(guān)鍵差異。
響應(yīng)式 | 模板驅(qū)動 | |
---|---|---|
建立表單模型 | 顯式的,在組件類中創(chuàng)建 | 隱式的,由指令創(chuàng)建 |
數(shù)據(jù)模型 | 結(jié)構(gòu)化和不可變的 | 非結(jié)構(gòu)化和可變的 |
可預(yù)測性 | 同步 | 異步 |
表單驗(yàn)證 | 函數(shù) | 指令 |
如果表單是應(yīng)用程序的核心部分,那么可伸縮性就非常重要。能夠跨組件復(fù)用表單模型是至關(guān)重要的。
響應(yīng)式表單比模板驅(qū)動表單更有可伸縮性。它們提供對底層表單 API 的直接訪問,以及對表單數(shù)據(jù)模型的同步訪問,從而可以更輕松地創(chuàng)建大型表單。響應(yīng)式表單需要較少的測試設(shè)置,測試時不需要深入理解變更檢測,就能正確測試表單更新和驗(yàn)證。
模板驅(qū)動表單專注于簡單的場景,可復(fù)用性沒那么高。它們抽象出了底層表單 API,并且只提供對表單數(shù)據(jù)模型的異步訪問。對模板驅(qū)動表單的這種抽象也會影響測試。測試程序非常依賴于手動觸發(fā)變更檢測才能正常運(yùn)行,并且需要進(jìn)行更多設(shè)置工作。
響應(yīng)式表單和模板驅(qū)動型表單都會跟蹤用戶與之交互的表單輸入元素和組件模型中的表單數(shù)據(jù)之間的值變更。這兩種方法共享同一套底層構(gòu)建塊,只在如何創(chuàng)建和管理常用表單控件實(shí)例方面有所不同。
響應(yīng)式表單和模板驅(qū)動表單都建立在下列基礎(chǔ)類之上。
FormControl
實(shí)例用于追蹤單個表單控件的值和驗(yàn)證狀態(tài)。FormGroup
用于追蹤一個表單控件組的值和狀態(tài)。FormArray
用于追蹤表單控件數(shù)組的值和狀態(tài)。ControlValueAccessor
用于在 Angular 的 FormControl
實(shí)例和原生 DOM 元素之間創(chuàng)建一個橋梁。
對于響應(yīng)式表單,你可以直接在組件類中定義表單模型。[formControl]
指令會通過內(nèi)部值訪問器來把顯式創(chuàng)建的 FormControl
實(shí)例與視圖中的特定表單元素聯(lián)系起來。
下面的組件使用響應(yīng)式表單為單個控件實(shí)現(xiàn)了一個輸入字段。在這個例子中,表單模型是 FormControl
實(shí)例。
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-reactive-favorite-color',
template: `
Favorite Color: <input type="text" [formControl]="favoriteColorControl">
`
})
export class FavoriteColorComponent {
favoriteColorControl = new FormControl('');
}
下圖展示了在響應(yīng)式表單中直接訪問表單模型。它通過輸入元素上的 [formControl]
指令,在任何給定的時間點(diǎn)提供表單元素的值和狀態(tài)。
在模板驅(qū)動表單中,表單模型是隱式的,而不是顯式的。指令 NgModel
為指定的表單元素創(chuàng)建并管理一個 FormControl
實(shí)例。
下面的組件使用模板驅(qū)動表單為單個控件實(shí)現(xiàn)了同樣的輸入字段。
import { Component } from '@angular/core';
@Component({
selector: 'app-template-favorite-color',
template: `
Favorite Color: <input type="text" [(ngModel)]="favoriteColor">
`
})
export class FavoriteColorComponent {
favoriteColor = '';
}
在模板驅(qū)動表單中,對表單模型的間接訪問。你沒有對 FormControl 實(shí)例的直接編程訪問,如下圖所示。
當(dāng)應(yīng)用包含一個表單時,Angular 必須讓該視圖與組件模型保持同步,并讓組件模型與視圖保持同步。當(dāng)用戶通過視圖更改值并進(jìn)行選擇時,新值必須反映在數(shù)據(jù)模型中。同樣,當(dāng)程序邏輯改變數(shù)據(jù)模型中的值時,這些值也必須反映到視圖中。
響應(yīng)式表單和模板驅(qū)動表單在處理來自用戶或程序化變更時的數(shù)據(jù)處理方式上有所不同。下面的這些示意圖會以上面定義的 favorite-color 輸入字段為例,分別說明兩種表單各自的數(shù)據(jù)流。
在響應(yīng)式表單中,視圖中的每個表單元素都直接鏈接到一個表單模型(FormControl 實(shí)例)。 從視圖到模型的修改以及從模型到視圖的修改都是同步的,而且不依賴于 UI 的渲染方式。
這個視圖到模型的示意圖展示了當(dāng)輸入字段的值發(fā)生變化時數(shù)據(jù)是如何從視圖開始,經(jīng)過下列步驟進(jìn)行流動的。
"input"
事件。ControlValueAccessor
會監(jiān)聽表單輸入框元素上的事件,并立即把新值傳給 FormControl
實(shí)例。FormControl
實(shí)例會通過 valueChanges
這個可觀察對象發(fā)出這個新值。valueChanges
的任何一個訂閱者都會收到這個新值。
這個模型到視圖的示意圖體現(xiàn)了程序中對模型的修改是如何通過下列步驟傳播到視圖中的。
favoriteColorControl.setValue()
方法被調(diào)用,它會更新這個 FormControl
的值。FormControl
實(shí)例會通過 valueChanges
這個可觀察對象發(fā)出新值。valueChanges
的任何訂閱者都會收到這個新值。
在模板驅(qū)動表單中,每一個表單元素都是和一個負(fù)責(zé)管理內(nèi)部表單模型的指令關(guān)聯(lián)起來的。
這個視圖到模型的圖表展示了當(dāng)輸入字段的值發(fā)生變化時,數(shù)據(jù)流是如何從視圖開始經(jīng)過下列步驟進(jìn)行流動的。
"input"
事件,帶著值 "Blue"。FormControl
實(shí)例上的 setValue()
方法。FormControl
實(shí)例通過 valueChanges
這個可觀察對象發(fā)出新值。valueChanges
的任何訂閱者都會收到新值。ControlValueAccessory
還會調(diào)用 NgModel.viewToModelUpdate()
方法,它會發(fā)出一個 ngModelChange
事件。favoriteColor
,組件中的 favoriteColor
屬性就會修改為 ngModelChange
事件所發(fā)出的值("Blue")。
這個模型到視圖的示意圖展示了當(dāng) favoriteColor
從藍(lán)變到紅時,數(shù)據(jù)是如何經(jīng)過如下步驟從模型流動到視圖的。
favoriteColor
的值。NgModel
指令上的 ngOnChanges
生命周期鉤子。ngOnChanges()
方法會把一個異步任務(wù)排入隊(duì)列,以設(shè)置內(nèi)部 FormControl
實(shí)例的值。FormControl
實(shí)例賦值的任務(wù)就會執(zhí)行。FormControl
實(shí)例通過可觀察對象 valueChanges
發(fā)出最新值。valueChanges
的任何訂閱者都會收到這個新值。ControlValueAccessor
會使用 favoriteColor
的最新值來修改表單的輸入框元素。
變更追蹤的方法對應(yīng)用的效率有著重要影響。
FormControl
實(shí)例都會返回一個新的數(shù)據(jù)模型,而不會更新現(xiàn)有的數(shù)據(jù)模型。這使你能夠通過該控件的可觀察對象跟蹤對數(shù)據(jù)模型的唯一更改。這讓變更檢測更有效率,因?yàn)樗恍柙谖ㄒ恍愿模ㄗg注:也就是對象引用發(fā)生變化)時進(jìn)行更新。由于數(shù)據(jù)更新遵循響應(yīng)式模式,因此你可以把它和可觀察對象的各種運(yùn)算符集成起來以轉(zhuǎn)換數(shù)據(jù)。
前面那些使用 favorite-color
輸入元素的例子就演示了這種差異。
FormControl
的實(shí)例總會返回一個新值。favorite-color
屬性總會被修改為新值。驗(yàn)證是管理任何表單時必備的一部分。無論你是要檢查必填項(xiàng),還是查詢外部 API 來檢查用戶名是否已存在,Angular 都會提供一組內(nèi)置的驗(yàn)證器,以及創(chuàng)建自定義驗(yàn)證器所需的能力。
測試在復(fù)雜的應(yīng)用程序中也起著重要的作用。當(dāng)驗(yàn)證你的表單功能是否正確時,更簡單的測試策略往往也更有用。測試響應(yīng)式表單和模板驅(qū)動表單的差別之一在于它們是否需要渲染 UI 才能基于表單控件和表單字段變化來執(zhí)行斷言。下面的例子演示了使用響應(yīng)式表單和模板驅(qū)動表單時表單的測試過程。
響應(yīng)式表單提供了相對簡單的測試策略,因?yàn)樗鼈兡芴峁Ρ韱魏蛿?shù)據(jù)模型的同步訪問,而且不必渲染 UI 就能測試它們。在這些測試中,控件和數(shù)據(jù)是通過控件進(jìn)行查詢和操縱的,不需要和變更檢測周期打交道。
下面的測試?yán)们懊胬又械?"喜歡的顏色" 組件來驗(yàn)證響應(yīng)式表單中的 "從視圖到模型" 和 "從模型到視圖" 數(shù)據(jù)流。
驗(yàn)證“從視圖到模型”的數(shù)據(jù)流
第一個例子執(zhí)行了下列步驟來驗(yàn)證“從視圖到模型”數(shù)據(jù)流。
"input"
事件"input"
事件。favoriteColorControl
的值與來自輸入框的值是匹配的。//Favorite color test - view to model
it('should update the value of the input field', () => {
const input = fixture.nativeElement.querySelector('input');
const event = createNewEvent('input');
input.value = 'Red';
input.dispatchEvent(event);
expect(fixture.componentInstance.favoriteColorControl.value).toEqual('Red');
});
驗(yàn)證“從模型到視圖”數(shù)據(jù)流:
favoriteColorControl
這個 FormControl
實(shí)例來設(shè)置新值。//Favorite color test - model to view
it('should update the value in the control', () => {
component.favoriteColorControl.setValue('Blue');
const input = fixture.nativeElement.querySelector('input');
expect(input.value).toBe('Blue');
});
使用模板驅(qū)動表單編寫測試就需要詳細(xì)了解變更檢測過程,以及指令在每個變更檢測周期中如何運(yùn)行,以確保在正確的時間查詢、測試或更改元素。
下面的測試使用了以前的 "喜歡的顏色" 組件,來驗(yàn)證模板驅(qū)動表單的 "從視圖到模型" 和 "從模型到視圖" 數(shù)據(jù)流。
驗(yàn)證 "從視圖到模型" 數(shù)據(jù)流:
"input"
事件。"input"
事件。favoriteColor
屬性的值與來自輸入框的值是匹配的。//Favorite color test - view to model
it('should update the favorite color in the component', fakeAsync(() => {
const input = fixture.nativeElement.querySelector('input');
const event = createNewEvent('input');
input.value = 'Red';
input.dispatchEvent(event);
fixture.detectChanges();
expect(component.favoriteColor).toEqual('Red');
}));
驗(yàn)證“從模型到視圖”數(shù)據(jù)流:
favoriteColor
的值。fakeAsync()
任務(wù)中使用 tick()
方法來模擬時間的流逝。favoriteColor
屬性值是匹配的。//Favorite color test - model to view
it('should update the favorite color on the input field', fakeAsync(() => {
component.favoriteColor = 'Blue';
fixture.detectChanges();
tick();
const input = fixture.nativeElement.querySelector('input');
expect(input.value).toBe('Blue');
}));
更多建議: