Angular9 響應(yīng)式表單

2020-07-01 16:53 更新

響應(yīng)式表單提供了一種模型驅(qū)動(dòng)的方式來處理表單輸入,其中的值會(huì)隨時(shí)間而變化。本文會(huì)向你展示如何創(chuàng)建和更新基本的表單控件,接下來還會(huì)在一個(gè)表單組中使用多個(gè)控件,驗(yàn)證表單的值,以及創(chuàng)建動(dòng)態(tài)表單,也就是在運(yùn)行期添加或移除控件。

先決條件

在深入了解被動(dòng)表單之前,你應(yīng)該對(duì)這些內(nèi)容有一個(gè)基本的了解:

  • TypeScript 編程。

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

  • “表單簡(jiǎn)介”中提供的表單設(shè)計(jì)概念。

響應(yīng)式表單概述

響應(yīng)式表單使用顯式的、不可變的方式,管理表單在特定的時(shí)間點(diǎn)上的狀態(tài)。對(duì)表單狀態(tài)的每一次變更都會(huì)返回一個(gè)新的狀態(tài),這樣可以在變化時(shí)維護(hù)模型的整體性。響應(yīng)式表單是圍繞 Observable 流構(gòu)建的,表單的輸入和值都是通過這些輸入值組成的流來提供的,它可以同步訪問。

響應(yīng)式表單還提供了一種更直觀的測(cè)試路徑,因?yàn)樵谡?qǐng)求時(shí)你可以確信這些數(shù)據(jù)是一致的、可預(yù)料的。這個(gè)流的任何一個(gè)消費(fèi)者都可以安全地操縱這些數(shù)據(jù)。

響應(yīng)式表單與模板驅(qū)動(dòng)表單有著顯著的不同點(diǎn)。響應(yīng)式表單通過對(duì)數(shù)據(jù)模型的同步訪問提供了更多的可預(yù)測(cè)性,使用 Observable 的操作符提供了不可變性,并且通過 Observable 流提供了變化追蹤功能。

模板驅(qū)動(dòng)的表單允許你直接在模板中修改數(shù)據(jù),但不像響應(yīng)式表單那么明確,因?yàn)樗鼈円蕾嚽度氲侥0逯械闹噶?,并借助可變?shù)據(jù)來異步跟蹤變化。參見表單概覽以了解這兩種范式之間的詳細(xì)比較。

添加基礎(chǔ)表單控件

使用表單控件有三個(gè)步驟。

  1. 在你的應(yīng)用中注冊(cè)響應(yīng)式表單模塊。該模塊聲明了一些你要用在響應(yīng)式表單中的指令。

  1. 生成一個(gè)新的 FormControl 實(shí)例,并把它保存在組件中。

  1. 在模板中注冊(cè)這個(gè) FormControl。

然后,你可以把組件添加到模板中來顯示表單。

下面的例子展示了如何添加一個(gè)表單控件。在這個(gè)例子中,用戶在輸入字段中輸入自己的名字,捕獲其輸入值,并顯示表單控件的當(dāng)前值。

注冊(cè)響應(yīng)式表單模塊

要使用響應(yīng)式表單控件,就要從 @angular/forms 包中導(dǎo)入 ReactiveFormsModule,并把它添加到你的 NgModuleimports 數(shù)組中。

Path:"src/app/app.module.ts (excerpt)" 。

import { ReactiveFormsModule } from '@angular/forms';


@NgModule({
  imports: [
    // other imports ...
    ReactiveFormsModule
  ],
})
export class AppModule { }

生成一個(gè)新的 FormControl

使用 CLI 命令 ng generate 在項(xiàng)目中生成一個(gè)組件作為該表單控件的宿主。

ng generate component NameEditor

要注冊(cè)一個(gè)表單控件,就要導(dǎo)入 FormControl 類并創(chuàng)建一個(gè) FormControl 的新實(shí)例,將其保存為類的屬性。

Path:"src/app/name-editor/name-editor.component.ts" 。

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';


@Component({
  selector: 'app-name-editor',
  templateUrl: './name-editor.component.html',
  styleUrls: ['./name-editor.component.css']
})
export class NameEditorComponent {
  name = new FormControl('');
}

可以用 FormControl 的構(gòu)造函數(shù)設(shè)置初始值,這個(gè)例子中它是空字符串。通過在你的組件類中創(chuàng)建這些控件,你可以直接對(duì)表單控件的狀態(tài)進(jìn)行監(jiān)聽、修改和校驗(yàn)。

在模板中注冊(cè)該控件

在組件類中創(chuàng)建了控件之后,你還要把它和模板中的一個(gè)表單控件關(guān)聯(lián)起來。修改模板,為表單控件添加 formControl 綁定,formControl 是由 ReactiveFormsModule 中的 FormControlDirective 提供的。

Path:"src/app/name-editor/name-editor.component.html" 。

<label>
  Name:
  <input type="text" [formControl]="name">
</label>

使用這種模板綁定語法,把該表單控件注冊(cè)給了模板中名為 name 的輸入元素。這樣,表單控件和 DOM 元素就可以互相通訊了:視圖會(huì)反映模型的變化,模型也會(huì)反映視圖中的變化。

顯示該組件

把該組件添加到模板時(shí),將顯示指派給 name 的表單控件。

Path:"src/app/app.component.html (name editor)" 。

<app-name-editor></app-name-editor>

顯示表單控件的值

你可以用下列方式顯示它的值:

  • 通過可觀察對(duì)象 valueChanges,你可以在模板中使用 AsyncPipe 或在組件類中使用 subscribe() 方法來監(jiān)聽表單值的變化。

  • 使用 value 屬性。它能讓你獲得當(dāng)前值的一份快照。

下面的例子展示了如何在模板中使用插值顯示當(dāng)前值。

Path:"src/app/name-editor/name-editor.component.html (control value)" 。

<p>
  Value: {{ name.value }}
</p>

一旦你修改了表單控件所關(guān)聯(lián)的元素,這里顯示的值也跟著變化了。

響應(yīng)式表單還能通過每個(gè)實(shí)例的屬性和方法提供關(guān)于特定控件的更多信息。AbstractControl 的這些屬性和方法用于控制表單狀態(tài),并在處理表單校驗(yàn)時(shí)決定何時(shí)顯示信息。

替換表單控件的值

響應(yīng)式表單還有一些方法可以用編程的方式修改控件的值,它讓你可以靈活的修改控件的值而不需要借助用戶交互。FormControl 提供了一個(gè) setValue() 方法,它會(huì)修改這個(gè)表單控件的值,并且驗(yàn)證與控件結(jié)構(gòu)相對(duì)應(yīng)的值的結(jié)構(gòu)。比如,當(dāng)從后端 API 或服務(wù)接收到了表單數(shù)據(jù)時(shí),可以通過 setValue() 方法來把原來的值替換為新的值。

下列的例子往組件類中添加了一個(gè)方法,它使用 setValue() 方法來修改 Nancy 控件的值。

Path:"src/app/name-editor/name-editor.component.ts (update value)" 。

updateName() {
  this.name.setValue('Nancy');
}

修改模板,添加一個(gè)按鈕,用于模擬改名操作。在點(diǎn) Update Name 按鈕之前表單控件元素中輸入的任何值都會(huì)回顯為它的當(dāng)前值。

Path:"src/app/name-editor/name-editor.component.html (update value)" 。

<p>
  <button (click)="updateName()">Update Name</button>
</p>

由于表單模型是該控件的事實(shí)之源,因此當(dāng)你單擊該按鈕時(shí),組件中該輸入框的值也變化了,覆蓋掉它的當(dāng)前值。

注:
- 在這個(gè)例子中,你只使用單個(gè)控件,但是當(dāng)調(diào)用 FormGroupFormArray 實(shí)例的 setValue() 方法時(shí),傳入的值就必須匹配控件組或控件數(shù)組的結(jié)構(gòu)才行。

把表單控件分組

表單中通常會(huì)包含幾個(gè)相互關(guān)聯(lián)的控件。響應(yīng)式表單提供了兩種把多個(gè)相關(guān)控件分組到同一個(gè)輸入表單中的方法。

  • 表單組定義了一個(gè)帶有一組控件的表單,你可以把它們放在一起管理。表單組的基礎(chǔ)知識(shí)將在本節(jié)中討論。你也可以通過嵌套表單組來創(chuàng)建更復(fù)雜的表單。

  • 表單數(shù)組定義了一個(gè)動(dòng)態(tài)表單,你可以在運(yùn)行時(shí)添加和刪除控件。你也可以通過嵌套表單數(shù)組來創(chuàng)建更復(fù)雜的表單。欲知詳情,參見下面的創(chuàng)建動(dòng)態(tài)表單。

就像 FormControl 的實(shí)例能讓你控制單個(gè)輸入框所對(duì)應(yīng)的控件一樣,FormGroup 的實(shí)例也能跟蹤一組 FormControl 實(shí)例(比如一個(gè)表單)的表單狀態(tài)。當(dāng)創(chuàng)建 FormGroup 時(shí),其中的每個(gè)控件都會(huì)根據(jù)其名字進(jìn)行跟蹤。下面的例子展示了如何管理單個(gè)控件組中的多個(gè) FormControl 實(shí)例。

生成一個(gè) ProfileEditor 組件并從 @angular/forms 包中導(dǎo)入 FormGroupFormControl 類。

ng generate component ProfileEditor

Path:"src/app/profile-editor/profile-editor.component.ts (imports)" 。

import { FormGroup, FormControl } from '@angular/forms';

要將表單組添加到此組件中,請(qǐng)執(zhí)行以下步驟。

  1. 創(chuàng)建一個(gè) FormGroup 實(shí)例。

  1. 把這個(gè) FormGroup 模型關(guān)聯(lián)到視圖。

  1. 保存表單數(shù)據(jù)。

創(chuàng)建一個(gè) FormGroup 實(shí)例

在組件類中創(chuàng)建一個(gè)名叫 profileForm 的屬性,并設(shè)置為 FormGroup 的一個(gè)新實(shí)例。要初始化這個(gè) FormGroup,請(qǐng)為構(gòu)造函數(shù)提供一個(gè)由控件組成的對(duì)象,對(duì)象中的每個(gè)名字都要和表單控件的名字一一對(duì)應(yīng)。

對(duì)此個(gè)人檔案表單,要添加兩個(gè) FormControl 實(shí)例,名字分別為 firstNamelastName。

Path:"src/app/profile-editor/profile-editor.component.ts (form group)" 。

import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';


@Component({
  selector: 'app-profile-editor',
  templateUrl: './profile-editor.component.html',
  styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
  profileForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
  });
}

現(xiàn)在,這些獨(dú)立的表單控件被收集到了一個(gè)控件組中。這個(gè) FormGroup 用對(duì)象的形式提供了它的模型值,這個(gè)值來自組中每個(gè)控件的值。 FormGroup 實(shí)例擁有和 FormControl 實(shí)例相同的屬性(比如 value、untouched)和方法(比如 setValue())。

把這個(gè) FormGroup 模型關(guān)聯(lián)到視圖。

這個(gè)表單組還能跟蹤其中每個(gè)控件的狀態(tài)及其變化,所以如果其中的某個(gè)控件的狀態(tài)或值變化了,父控件也會(huì)發(fā)出一次新的狀態(tài)變更或值變更事件。該控件組的模型來自它的所有成員。在定義了這個(gè)模型之后,你必須更新模板,來把該模型反映到視圖中。

Path:"src/app/profile-editor/profile-editor.component.html (template form group)" 。

<form [formGroup]="profileForm">

  
  <label>
    First Name:
    <input type="text" formControlName="firstName">
  </label>


  <label>
    Last Name:
    <input type="text" formControlName="lastName">
  </label>


</form>

注意,就像 FormGroup 所包含的那控件一樣,profileForm 這個(gè) FormGroup 也通過 FormGroup 指令綁定到了 form 元素,在該模型和表單中的輸入框之間創(chuàng)建了一個(gè)通訊層。 由 FormControlName 指令提供的 formControlName 屬性把每個(gè)輸入框和 FormGroup 中定義的表單控件綁定起來。這些表單控件會(huì)和相應(yīng)的元素通訊,它們還把更改傳給 FormGroup,這個(gè) FormGroup 是模型值的事實(shí)之源。

保存表單數(shù)據(jù)

ProfileEditor 組件從用戶那里獲得輸入,但在真實(shí)的場(chǎng)景中,你可能想要先捕獲表單的值,等將來在組件外部進(jìn)行處理。 FormGroup 指令會(huì)監(jiān)聽 form 元素發(fā)出的 submit 事件,并發(fā)出一個(gè) ngSubmit 事件,讓你可以綁定一個(gè)回調(diào)函數(shù)。

onSubmit() 回調(diào)方法添加為 form 標(biāo)簽上的 ngSubmit 事件監(jiān)聽器。

Path:"src/app/profile-editor/profile-editor.component.html (submit event)" 。

<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">

ProfileEditor 組件上的 onSubmit() 方法會(huì)捕獲 profileForm 的當(dāng)前值。要保持該表單的封裝性,就要使用 EventEmitter 向組件外部提供該表單的值。下面的例子會(huì)使用 console.warn 把這個(gè)值記錄到瀏覽器的控制臺(tái)中。

Path:"src/app/profile-editor/profile-editor.component.ts (submit method)" 。

onSubmit() {
  // TODO: Use EventEmitter with form value
  console.warn(this.profileForm.value);
}

form 標(biāo)簽所發(fā)出的 submit 事件是原生 DOM 事件,通過點(diǎn)擊類型為 submit 的按鈕可以觸發(fā)本事件。這還讓用戶可以用回車鍵來提交填完的表單。

往表單的底部添加一個(gè) button,用于觸發(fā)表單提交。

Path:"src/app/profile-editor/profile-editor.component.html (submit button)" 。

<button type="submit" [disabled]="!profileForm.valid">Submit</button>

&上面這個(gè)代碼片段中的按鈕還附加了一個(gè) disabled 綁定,用于在 profileForm 無效時(shí)禁用該按鈕。目前你還沒有執(zhí)行任何表單驗(yàn)證邏輯,因此該按鈕始終是可用的。稍后的驗(yàn)證表單輸入部分會(huì)講解基礎(chǔ)的表單驗(yàn)證。

顯示組件

要顯示包含此表單的 ProfileEditor 組件,請(qǐng)把它添加到組件模板中。

Path:"src/app/app.component.html (profile editor)" 。

<app-profile-editor></app-profile-editor>

ProfileEditor 讓你能管理 FormGroup 中的 firstNamelastNameFormControl 實(shí)例。

創(chuàng)建嵌套的表單組

表單組可以同時(shí)接受單個(gè)表單控件實(shí)例和其它表單組實(shí)例作為其子控件。這可以讓復(fù)雜的表單模型更容易維護(hù),并在邏輯上把它們分組到一起。

如果要構(gòu)建復(fù)雜的表單,如果能在更小的分區(qū)中管理不同類別的信息就會(huì)更容易一些。使用嵌套的 FormGroup 可以讓你把大型表單組織成一些稍小的、易管理的分組。

要制作更復(fù)雜的表單,請(qǐng)遵循如下步驟。

創(chuàng)建一個(gè)嵌套的表單組。

在模板中對(duì)這個(gè)嵌套表單分組。

某些類型的信息天然就屬于同一個(gè)組。比如名稱和地址就是這類嵌套組的典型例子,下面的例子中就用到了它們。

創(chuàng)建一個(gè)嵌套組

要在 profileForm 中創(chuàng)建一個(gè)嵌套組,就要把一個(gè)嵌套的 address 元素添加到此表單組的實(shí)例中。

Path:"src/app/profile-editor/profile-editor.component.ts (nested form group)" 。

import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';


@Component({
  selector: 'app-profile-editor',
  templateUrl: './profile-editor.component.html',
  styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
  profileForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
    address: new FormGroup({
      street: new FormControl(''),
      city: new FormControl(''),
      state: new FormControl(''),
      zip: new FormControl('')
    })
  });
}

在這個(gè)例子中,address group 把現(xiàn)有的 firstNamelastName 控件和新的 street、city、statezip 控件組合在一起。雖然 address 這個(gè) FormGroupprofileForm 這個(gè)整體 FormGroup 的一個(gè)子控件,但是仍然適用同樣的值和狀態(tài)的變更規(guī)則。來自內(nèi)嵌控件組的狀態(tài)和值的變更將會(huì)冒泡到它的父控件組,以維護(hù)整體模型的一致性。

在模板中對(duì)此嵌套表單分組

在修改了組件類中的模型之后,還要修改模板,來把這個(gè) FormGroup 實(shí)例對(duì)接到它的輸入元素。

把包含 street、citystatezip 字段的 address 表單組添加到 ProfileEditor 模板中。

Path:"src/app/profile-editor/profile-editor.component.html (template nested form group)" 。

<div formGroupName="address">
  <h3>Address</h3>


  <label>
    Street:
    <input type="text" formControlName="street">
  </label>


  <label>
    City:
    <input type="text" formControlName="city">
  </label>

  
  <label>
    State:
    <input type="text" formControlName="state">
  </label>


  <label>
    Zip Code:
    <input type="text" formControlName="zip">
  </label>
</div>

ProfileEditor 表單顯示為一個(gè)組,但是將來這個(gè)模型會(huì)被進(jìn)一步細(xì)分,以表示邏輯分組區(qū)域。

注:

  • 這里使用了 value 屬性和 JsonPipe 管道在組件模板中顯示了這個(gè) FormGroup 的值。

更新部分?jǐn)?shù)據(jù)模型

當(dāng)修改包含多個(gè) FormGroup 實(shí)例的值時(shí),你可能只希望更新模型中的一部分,而不是完全替換掉。這一節(jié)會(huì)講解該如何更新 AbstractControl 模型中的一部分。

有兩種更新模型值的方式:

  • 使用 setValue() 方法來為單個(gè)控件設(shè)置新值。 setValue() 方法會(huì)嚴(yán)格遵循表單組的結(jié)構(gòu),并整體性替換控件的值。

  • 使用 patchValue() 方法可以用對(duì)象中所定義的任何屬性為表單模型進(jìn)行替換。

setValue() 方法的嚴(yán)格檢查可以幫助你捕獲復(fù)雜表單嵌套中的錯(cuò)誤,而 patchValue() 在遇到那些錯(cuò)誤時(shí)可能會(huì)默默的失敗。

ProfileEditorComponent 中,使用 updateProfile 方法傳入下列數(shù)據(jù)可以更新用戶的名字與街道住址。

Path:"src/app/profile-editor/profile-editor.component.ts (patch value)" 。

updateProfile() {
  this.profileForm.patchValue({
    firstName: 'Nancy',
    address: {
      street: '123 Drew Street'
    }
  });
}

通過往模板中添加一個(gè)按鈕來模擬一次更新操作,以修改用戶檔案。

Path:"src/app/profile-editor/profile-editor.component.html (update value)" 。

<p>
  <button (click)="updateProfile()">Update Profile</button>
</p>

當(dāng)點(diǎn)擊按鈕時(shí),profileForm 模型中只有 firstNamestreet 被修改了。注意,street 是在 address 屬性的對(duì)象中被修改的。這種結(jié)構(gòu)是必須的,因?yàn)?patchValue() 方法要針對(duì)模型的結(jié)構(gòu)進(jìn)行更新。patchValue() 只會(huì)更新表單模型中所定義的那些屬性。

使用 FormBuilder 服務(wù)生成控件

當(dāng)需要與多個(gè)表單打交道時(shí),手動(dòng)創(chuàng)建多個(gè)表單控件實(shí)例會(huì)非常繁瑣。FormBuilder 服務(wù)提供了一些便捷方法來生成表單控件。FormBuilder 在幕后也使用同樣的方式來創(chuàng)建和返回這些實(shí)例,只是用起來更簡(jiǎn)單。

通過下列步驟可以利用這項(xiàng)服務(wù)。

導(dǎo)入 FormBuilder 類。

注入這個(gè) FormBuilder 服務(wù)。

生成表單內(nèi)容。

下面的例子展示了如何重構(gòu) ProfileEditor 組件,用 FormBuilder 來代替手工創(chuàng)建這些 FormControlFormGroup 實(shí)例。

導(dǎo)入 FormBuilder 類

@angular/forms 包中導(dǎo)入 FormBuilder 類。

Path:"src/app/profile-editor/profile-editor.component.ts (import)" 。

import { FormBuilder } from '@angular/forms';

注入 FormBuilder 服務(wù)

FormBuilder 是一個(gè)可注入的服務(wù)提供者,它是由 ReactiveFormModule 提供的。只要把它添加到組件的構(gòu)造函數(shù)中就可以注入這個(gè)依賴。

Path:"src/app/profile-editor/profile-editor.component.ts (constructor)" 。

constructor(private fb: FormBuilder) { }

生成表單控件

FormBuilder 服務(wù)有三個(gè)方法:control()、group()array()。這些方法都是工廠方法,用于在組件類中分別生成 FormControl、FormGroupFormArray

group 方法來創(chuàng)建 profileForm 控件。

Path:"src/app/profile-editor/profile-editor.component.ts (form builder)" 。

import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';


@Component({
  selector: 'app-profile-editor',
  templateUrl: './profile-editor.component.html',
  styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
  profileForm = this.fb.group({
    firstName: [''],
    lastName: [''],
    address: this.fb.group({
      street: [''],
      city: [''],
      state: [''],
      zip: ['']
    }),
  });


  constructor(private fb: FormBuilder) { }
}

在上面的例子中,你可以使用 group() 方法,用和前面一樣的名字來定義這些屬性。這里,每個(gè)控件名對(duì)應(yīng)的值都是一個(gè)數(shù)組,這個(gè)數(shù)組中的第一項(xiàng)是其初始值。

&你可以只使用初始值來定義控件,但是如果你的控件還需要同步或異步驗(yàn)證器,那就在這個(gè)數(shù)組中的第二項(xiàng)和第三項(xiàng)提供同步和異步驗(yàn)證器。

比較一下用表單構(gòu)建器和手動(dòng)創(chuàng)建實(shí)例這兩種方式。

  1. Path:"src/app/profile-editor/profile-editor.component.ts (instances)" 。

    profileForm = new FormGroup({
      firstName: new FormControl(''),
      lastName: new FormControl(''),
      address: new FormGroup({
        street: new FormControl(''),
        city: new FormControl(''),
        state: new FormControl(''),
        zip: new FormControl('')
      })
    });

  1. Path:"src/app/profile-editor/profile-editor.component.ts (form builder)" 。

    profileForm = this.fb.group({
      firstName: [''],
      lastName: [''],
      address: this.fb.group({
        street: [''],
        city: [''],
        state: [''],
        zip: ['']
      }),
    });

驗(yàn)證表單輸入

表單驗(yàn)證用于確保用戶的輸入是完整和正確的。本節(jié)講解了如何把單個(gè)驗(yàn)證器添加到表單控件中,以及如何顯示表單的整體狀態(tài)。表單驗(yàn)證的更多知識(shí)在表單驗(yàn)證一章中有詳細(xì)的講解。

使用下列步驟添加表單驗(yàn)證。

  1. 在表單組件中導(dǎo)入一個(gè)驗(yàn)證器函數(shù)。

  1. 把這個(gè)驗(yàn)證器添加到表單中的相應(yīng)字段。

  1. 添加邏輯來處理驗(yàn)證狀態(tài)。

最常見的驗(yàn)證是做一個(gè)必填字段。下面的例子給出了如何在 firstName 控件中添加必填驗(yàn)證并顯示驗(yàn)證結(jié)果的方法。

導(dǎo)入驗(yàn)證器函數(shù)

響應(yīng)式表單包含了一組開箱即用的常用驗(yàn)證器函數(shù)。這些函數(shù)接收一個(gè)控件,用以驗(yàn)證并根據(jù)驗(yàn)證結(jié)果返回一個(gè)錯(cuò)誤對(duì)象或空值。

@angular/forms 包中導(dǎo)入 Validators 類。

Path:"src/app/profile-editor/profile-editor.component.ts (import)" 。

import { Validators } from '@angular/forms';

建一個(gè)必填字段

ProfileEditor 組件中,把靜態(tài)方法 Validators.required 設(shè)置為 firstName 控件值數(shù)組中的第二項(xiàng)。

Path:"src/app/profile-editor/profile-editor.component.ts (import)" 。

profileForm = this.fb.group({
  firstName: ['', Validators.required],
  lastName: [''],
  address: this.fb.group({
    street: [''],
    city: [''],
    state: [''],
    zip: ['']
  }),
});

HTML5 有一組內(nèi)置的屬性,用來進(jìn)行原生驗(yàn)證,包括 required、minlengthmaxlength 等。雖然是可選的,不過你也可以在表單的輸入元素上把它們添加為附加屬性來使用它們。這里我們把 required 屬性添加到 firstName 輸入元素上。

Path:"src/app/profile-editor/profile-editor.component.html (required attribute)" 。

<input type="text" formControlName="firstName" required>

注:

  • 這些 HTML5 驗(yàn)證器屬性可以和 Angular 響應(yīng)式表單提供的內(nèi)置驗(yàn)證器組合使用。組合使用這兩種驗(yàn)證器實(shí)踐,可以防止在模板檢查完之后表達(dá)式再次被修改導(dǎo)致的錯(cuò)誤。

顯示表單狀態(tài)

當(dāng)你往表單控件上添加了一個(gè)必填字段時(shí),它的初始值是無效的(invalid)。這種無效狀態(tài)會(huì)傳播到其父 FormGroup 元素中,也讓這個(gè) FormGroup 的狀態(tài)變?yōu)闊o效的。你可以通過該 FormGroup 實(shí)例的 status 屬性來訪問其當(dāng)前狀態(tài)。

使用插值顯示 profileForm 的當(dāng)前狀態(tài)。

Path:"src/app/profile-editor/profile-editor.component.html (display status)" 。

<p>
  Form Status: {{ profileForm.status }}
</p>

提交按鈕被禁用了,因?yàn)?firstName 控件的必填項(xiàng)規(guī)則導(dǎo)致了 profileForm 也是無效的。在你填寫了 firstName 輸入框之后,該表單就變成了有效的,并且提交按鈕也啟用了。

創(chuàng)建動(dòng)態(tài)表單

FormArrayFormGroup 之外的另一個(gè)選擇,用于管理任意數(shù)量的匿名控件。像 FormGroup 實(shí)例一樣,你也可以往 FormArray 中動(dòng)態(tài)插入和移除控件,并且 FormArray 實(shí)例的值和驗(yàn)證狀態(tài)也是根據(jù)它的子控件計(jì)算得來的。 不過,你不需要為每個(gè)控件定義一個(gè)名字作為 key,因此,如果你事先不知道子控件的數(shù)量,這就是一個(gè)很好的選擇。

要定義一個(gè)動(dòng)態(tài)表單,請(qǐng)執(zhí)行以下步驟。

  1. 導(dǎo)入 FormArray 類。

  1. 定義一個(gè) FormArray 控件。

  1. 使用 getter 方法訪問 FormArray 控件。

  1. 在模板中顯示這個(gè)表單數(shù)組。

下面的例子展示了如何在 ProfileEditor 中管理別名數(shù)組。

導(dǎo)入 FormArray 類

@angular/form 中導(dǎo)入 FormArray,以使用它的類型信息。FormBuilder 服務(wù)用于創(chuàng)建 FormArray 實(shí)例。

Path:"src/app/profile-editor/profile-editor.component.ts (import)" 。

import { FormArray } from '@angular/forms';

定義 FormArray 控件

你可以通過把一組(從零項(xiàng)到多項(xiàng))控件定義在一個(gè)數(shù)組中來初始化一個(gè) FormArray。為 profileForm 添加一個(gè) aliases 屬性,把它定義為 FormArray 類型。

使用 FormBuilder.array() 方法來定義該數(shù)組,并用 FormBuilder.control() 方法來往該數(shù)組中添加一個(gè)初始控件。

Path:"src/app/profile-editor/profile-editor.component.ts (aliases form array)" 。

profileForm = this.fb.group({
  firstName: ['', Validators.required],
  lastName: [''],
  address: this.fb.group({
    street: [''],
    city: [''],
    state: [''],
    zip: ['']
  }),
  aliases: this.fb.array([
    this.fb.control('')
  ])
});

FormGroup 中的這個(gè) aliases 控件現(xiàn)在管理著一個(gè)控件,將來還可以動(dòng)態(tài)添加多個(gè)。

訪問 FormArray 控件

相對(duì)于重復(fù)使用 profileForm.get() 方法獲取每個(gè)實(shí)例的方式,getter 可以讓你輕松訪問表單數(shù)組各個(gè)實(shí)例中的別名。 表單數(shù)組實(shí)例用一個(gè)數(shù)組來代表未定數(shù)量的控件。通過 getter 來訪問控件很方便,這種方法還能很容易地重復(fù)處理更多控件。

使用 getter 語法創(chuàng)建類屬性 aliases,以從父表單組中接收表示綽號(hào)的表單數(shù)組控件。

Path:"src/app/profile-editor/profile-editor.component.ts (aliases getter)" 。

get aliases() {
  return this.profileForm.get('aliases') as FormArray;
}

注:

  • 因?yàn)榉祷氐目丶念愋褪?code>AbstractControl,所以你要為該方法提供一個(gè)顯式的類型聲明來訪問 FormArray 特有的語法。

定義一個(gè)方法來把一個(gè)綽號(hào)控件動(dòng)態(tài)插入到綽號(hào) FormArray 中。用 FormArray.push() 方法把該控件添加為數(shù)組中的新條目。

Path:"src/app/profile-editor/profile-editor.component.ts (add alias)" 。

addAlias() {
  this.aliases.push(this.fb.control(''));
}

在這個(gè)模板中,這些控件會(huì)被迭代,把每個(gè)控件都顯示為一個(gè)獨(dú)立的輸入框。

在模板中顯示表單數(shù)組

要想為表單模型添加 aliases,你必須把它加入到模板中供用戶輸入。和 FormGroupNameDirective 提供的 formGroupName 一樣,FormArrayNameDirective 也使用 formArrayName 在這個(gè) FormArray 實(shí)例和模板之間建立綁定。

formGroupName <div> 元素的結(jié)束標(biāo)簽下方,添加一段模板 HTML。

Path:"src/app/profile-editor/profile-editor.component.html (aliases form array template)" 。

<div formArrayName="aliases">
  <h3>Aliases</h3> <button (click)="addAlias()">Add Alias</button>


  <div *ngFor="let alias of aliases.controls; let i=index">
    <!-- The repeated alias template -->
    <label>
      Alias:
      <input type="text" [formControlName]="i">
    </label>
  </div>
</div>

*ngFor 指令對(duì) aliases FormArray 提供的每個(gè) FormControl 進(jìn)行迭代。因?yàn)?FormArray 中的元素是匿名的,所以你要把索引號(hào)賦值給 i 變量,并且把它傳給每個(gè)控件的 formControlName 輸入屬性。

每當(dāng)新的 alias 加進(jìn)來時(shí),FormArray 的實(shí)例就會(huì)基于這個(gè)索引號(hào)提供它的控件。這將允許你在每次計(jì)算根控件的狀態(tài)和值時(shí)跟蹤每個(gè)控件。

添加一個(gè)別名

最初,表單只包含一個(gè)綽號(hào)字段,點(diǎn)擊 Add Alias 按鈕,就出現(xiàn)了另一個(gè)字段。你還可以驗(yàn)證由模板底部的“Form Value”顯示出來的表單模型所報(bào)告的這個(gè)綽號(hào)數(shù)組。

注:

  • 除了為每個(gè)綽號(hào)使用 FormControl 之外,你還可以改用 FormGroup 來組合上一些額外字段。對(duì)其中的每個(gè)條目定義控件的過程和前面沒有區(qū)別。

響應(yīng)式表單 API 匯總

下列表格給出了用于創(chuàng)建和管理響應(yīng)式表單控件的基礎(chǔ)類和服務(wù)。

說明
AbstractControl 所有三種表單控件類(FormControl、FormGroup 和 FormArray)的抽象基類。它提供了一些公共的行為和屬性。
FormControl 管理單體表單控件的值和有效性狀態(tài)。它對(duì)應(yīng)于 HTML 的表單控件,比如 <input> 或 <select>。
FormGroup 管理一組 AbstractControl 實(shí)例的值和有效性狀態(tài)。該組的屬性中包括了它的子控件。組件中的頂層表單就是 FormGroup。
FormArray 管理一些 AbstractControl 實(shí)例數(shù)組的值和有效性狀態(tài)。
FormBuilder 一個(gè)可注入的服務(wù),提供一些用于提供創(chuàng)建控件實(shí)例的工廠方法。

指令

指令 說明
FormControlDirective 把一個(gè)獨(dú)立的 FormControl 實(shí)例綁定到表單控件元素。
FormControlName 把一個(gè)現(xiàn)有 FormGroup 中的 FormControl 實(shí)例根據(jù)名字綁定到表單控件元素。
FormGroupDirective 把一個(gè)現(xiàn)有的 FormGroup 實(shí)例綁定到 DOM 元素。
FormGroupName 把一個(gè)內(nèi)嵌的 FormGroup 實(shí)例綁定到一個(gè) DOM 元素。
FormArrayName 把一個(gè)內(nèi)嵌的 FormArray 實(shí)例綁定到一個(gè) DOM 元素。
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)