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