Angular9 表單簡介

2020-07-02 14:52 更新

用表單處理用戶輸入是許多常見應(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)容有基本的了解:

  • TypeScript和 HTML5 編程。

  • Angular 的應(yīng)用設(shè)計基礎(chǔ),就像Angular Concepts 中描述的那樣。

  • Angular 模板語法的基礎(chǔ)知識。

選擇一種方法

響應(yīng)式表單和模板驅(qū)動表單以不同的方式處理和管理表單數(shù)據(jù)。每種方法都有各自的優(yōu)點(diǎn)。

  • 響應(yīng)式表單提供對底層表單對象模型直接、顯式的訪問。它們與模板驅(qū)動表單相比,更加健壯:它們的可擴(kuò)展性、可復(fù)用性和可測試性都更高。如果表單是你的應(yīng)用程序的關(guān)鍵部分,或者你已經(jīng)在使用響應(yīng)式表單來構(gòu)建應(yīng)用,那就使用響應(yīng)式表單。

  • 模板驅(qū)動表單依賴模板中的指令來創(chuàng)建和操作底層的對象模型。它們對于向應(yīng)用添加一個簡單的表單非常有用,比如電子郵件列表注冊表單。它們很容易添加到應(yīng)用中,但在擴(kuò)展性方面不如響應(yīng)式表單。如果你有可以只在模板中管理的非?;镜谋韱涡枨蠛瓦壿?,那么模板驅(qū)動表單就很合適。

關(guā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í)例方面有所不同。

常用表單基礎(chǔ)類

響應(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)式表單

對于響應(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ū)動表單

在模板驅(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í)例的直接編程訪問,如下圖所示。

表單中的數(shù)據(jù)流

當(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)式表單中的數(shù)據(jù)流

在響應(yīng)式表單中,視圖中的每個表單元素都直接鏈接到一個表單模型(FormControl 實(shí)例)。 從視圖到模型的修改以及從模型到視圖的修改都是同步的,而且不依賴于 UI 的渲染方式。

這個視圖到模型的示意圖展示了當(dāng)輸入字段的值發(fā)生變化時數(shù)據(jù)是如何從視圖開始,經(jīng)過下列步驟進(jìn)行流動的。

  1. 最終用戶在輸入框元素中鍵入了一個值,這里是 "Blue"。

  1. 這個輸入框元素會發(fā)出一個帶有最新值的 "input" 事件。

  1. 這個控件值訪問器 ControlValueAccessor 會監(jiān)聽表單輸入框元素上的事件,并立即把新值傳給 FormControl 實(shí)例。

  1. FormControl 實(shí)例會通過 valueChanges 這個可觀察對象發(fā)出這個新值。

  1. valueChanges 的任何一個訂閱者都會收到這個新值。

這個模型到視圖的示意圖體現(xiàn)了程序中對模型的修改是如何通過下列步驟傳播到視圖中的。

  1. favoriteColorControl.setValue() 方法被調(diào)用,它會更新這個 FormControl 的值。

  1. FormControl 實(shí)例會通過 valueChanges 這個可觀察對象發(fā)出新值。

  1. valueChanges 的任何訂閱者都會收到這個新值。

  1. 該表單輸入框元素上的控件值訪問器會把控件更新為這個新值。

模板驅(qū)動表單中的數(shù)據(jù)流

在模板驅(qū)動表單中,每一個表單元素都是和一個負(fù)責(zé)管理內(nèi)部表單模型的指令關(guān)聯(lián)起來的。

這個視圖到模型的圖表展示了當(dāng)輸入字段的值發(fā)生變化時,數(shù)據(jù)流是如何從視圖開始經(jīng)過下列步驟進(jìn)行流動的。

  1. 最終用戶在輸入框元素中敲 "Blue"。

  1. 該輸入框元素會發(fā)出一個 "input" 事件,帶著值 "Blue"。

  1. 附著在該輸入框上的控件值訪問器會觸發(fā) FormControl 實(shí)例上的 setValue() 方法。

  1. FormControl 實(shí)例通過 valueChanges 這個可觀察對象發(fā)出新值。

  1. valueChanges 的任何訂閱者都會收到新值。

  1. 控件值訪問器 ControlValueAccessory 還會調(diào)用 NgModel.viewToModelUpdate() 方法,它會發(fā)出一個 ngModelChange 事件。

  1. 由于該組件模板雙向數(shù)據(jù)綁定到了 favoriteColor,組件中的 favoriteColor 屬性就會修改為 ngModelChange 事件所發(fā)出的值("Blue")。

這個模型到視圖的示意圖展示了當(dāng) favoriteColor 從藍(lán)變到紅時,數(shù)據(jù)是如何經(jīng)過如下步驟從模型流動到視圖的。

  1. 組件中修改了 favoriteColor 的值。

  1. 變更檢測開始。

  1. 在變更檢測期間,由于這些輸入框之一的值發(fā)生了變化,Angular 就會調(diào)用 NgModel 指令上的 ngOnChanges 生命周期鉤子。

  1. ngOnChanges() 方法會把一個異步任務(wù)排入隊(duì)列,以設(shè)置內(nèi)部 FormControl 實(shí)例的值。

  1. 變更檢測完成。

  1. 在下一個檢測周期,用來為 FormControl 實(shí)例賦值的任務(wù)就會執(zhí)行。

  1. FormControl 實(shí)例通過可觀察對象 valueChanges 發(fā)出最新值。

  1. valueChanges 的任何訂閱者都會收到這個新值。

  1. 控件值訪問器 ControlValueAccessor 會使用 favoriteColor 的最新值來修改表單的輸入框元素。

數(shù)據(jù)模型的可變性

變更追蹤的方法對應(yīng)用的效率有著重要影響。

  • 響應(yīng)式表單通過以不可變的數(shù)據(jù)結(jié)構(gòu)提供數(shù)據(jù)模型,來保持?jǐn)?shù)據(jù)模型的純粹性。每當(dāng)在數(shù)據(jù)模型上觸發(fā)更改時,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ù)。

  • 模板驅(qū)動的表單依賴于可變性和雙向數(shù)據(jù)綁定,可以在模板中做出更改時更新組件中的數(shù)據(jù)模型。由于使用雙向數(shù)據(jù)綁定時沒有用來對數(shù)據(jù)模型進(jìn)行跟蹤的唯一性更改,因此變更檢測在需要確定何時更新時效率較低。

前面那些使用 favorite-color 輸入元素的例子就演示了這種差異。

  • 對于響應(yīng)式表單,當(dāng)控件值更新時,FormControl 的實(shí)例總會返回一個新值。

  • 對于模板驅(qū)動的表單,favorite-color 屬性總會被修改為新值。

表單驗(yàn)證

驗(yàn)證是管理任何表單時必備的一部分。無論你是要檢查必填項(xiàng),還是查詢外部 API 來檢查用戶名是否已存在,Angular 都會提供一組內(nèi)置的驗(yàn)證器,以及創(chuàng)建自定義驗(yàn)證器所需的能力。

  • 響應(yīng)式表單把自定義驗(yàn)證器定義成函數(shù),它以要驗(yàn)證的控件作為參數(shù)。

  • 模板驅(qū)動表單和模板指令緊密相關(guān),并且必須提供包裝了驗(yàn)證函數(shù)的自定義驗(yàn)證器指令。

測試

測試在復(fù)雜的應(yīng)用程序中也起著重要的作用。當(dāng)驗(yàn)證你的表單功能是否正確時,更簡單的測試策略往往也更有用。測試響應(yīng)式表單和模板驅(qū)動表單的差別之一在于它們是否需要渲染 UI 才能基于表單控件和表單字段變化來執(zhí)行斷言。下面的例子演示了使用響應(yīng)式表單和模板驅(qū)動表單時表單的測試過程。

測試響應(yīng)式表單

響應(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ù)流。

  1. 查詢表單輸入框元素的視圖,并為測試創(chuàng)建自定義的 "input" 事件

  1. 把輸入的新值設(shè)置為 Red,并在表單輸入元素上調(diào)度 "input" 事件。

  1. 斷言該組件的 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ù)流:

  1. 使用 favoriteColorControl 這個 FormControl 實(shí)例來設(shè)置新值。

  1. 查詢表單中輸入框的視圖。

  1. 斷言控件上設(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ū)動表單

使用模板驅(qū)動表單編寫測試就需要詳細(xì)了解變更檢測過程,以及指令在每個變更檢測周期中如何運(yùn)行,以確保在正確的時間查詢、測試或更改元素。

下面的測試使用了以前的 "喜歡的顏色" 組件,來驗(yàn)證模板驅(qū)動表單的 "從視圖到模型" 和 "從模型到視圖" 數(shù)據(jù)流。

驗(yàn)證 "從視圖到模型" 數(shù)據(jù)流:

  1. 查詢表單輸入元素中的視圖,并為測試創(chuàng)建自定義 "input" 事件。

  1. 把輸入框的新值設(shè)置為 Red,并在表單輸入框元素上派發(fā) "input" 事件。

  1. 通過測試夾具(Fixture)來運(yùn)行變更檢測。

  1. 斷言該組件 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ù)流:

  1. 使用組件實(shí)例來設(shè)置 favoriteColor 的值。

  1. 通過測試夾具(Fixture)來運(yùn)行變更檢測。

  1. fakeAsync() 任務(wù)中使用 tick() 方法來模擬時間的流逝。

  1. 查詢表單輸入框元素的視圖。

  1. 斷言輸入框的值與該組件實(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');
}));
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號