[從 0 開始的 Angular 生活]No.49 Angular 響應式表單 (Reactive forms) (二)

前言

承上篇,接著使用 FormBuilder 重構程式碼。

使用 FormBuilder 來產生表單控制元件

當需要與多個表單打交道時,手動建立多個表單控制元件例項會非常繁瑣。

FormBuilder 服務提供了一些便捷方法來產生表單控制元件,在幕後也使用同樣的方式來建立和返回這些實例,只是用起來更簡單。

接下來會重構 ProfileEditor 元件,用 FormBuilder 來建立這些 FormControl 和 FormGroup 實例。

匯入 FormBuilder 類

從 @angular/forms 包中匯入 FormBuilder 類。

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

注入 FormBuilder 服務

FormBuilder 是一個可注入的服務提供商,它是由 ReactiveFormModule 提供的。

只要把它新增到元件的建構函式中就可以注入這個依賴。

1
constructor(private fb: FormBuilder) { }

產生表單控制元件

FormBuilder 服務有三個方法:

  • control()
  • group()
  • array()

這些方法都是工廠方法,用於在元件的 class 中分別產生 FormControl 、 FormGroup 和 FormArray。

所以可以使用 group 方法建立 profileForm 控制元件。

而目前 profile-editor.component 的 class 是這樣的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, FormBuilder } from '@angular/forms';

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

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

不難看出與先前手動建立 FormControl 、 FormGroup 十分相似,而且省下了非常大量的 new 。

每個控制元件名對應的值都是一個陣列,而陣列中的第一項是其初始值。

可以只使用初始值來定義控制元件,但是如果控制元件還需要同步或非同步驗證器,那就在這個陣列中的第二項和第三項提供同步和非同步驗證器。

運行看看重構的結果吧!

執行成功

簡易表單驗證

表單驗證用於驗證使用者的輸入,以確保其完整和正確。

那麼該如何把單個驗證器新增到表單控制元件中,以及如何顯示表單的整體狀態呢?

匯入驗證器函式

響應式表單包含了一組內建的常用驗證器函式。

這些函式接收一個控制元件,用以驗證並根據驗證結果返回一個錯誤物件或空值。

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

把欄位設為必填(required)

檢查某個欄位有沒有被正確的填入值是相當常見的驗證:

  • 把 firstName 設為必填項目
  • 在 ProfileEditor 元件中,把靜態方法 Validators.required 設定為 firstName 控制元件值陣列中的第二項。
1
2
3
4
5
6
7
8
9
10
profileForm = this.fb.group({
firstName: ['', Validators.required],
lastName: [''],
address: this.fb.group({
street: [''],
city: [''],
state: [''],
zip: ['']
}),
});

然後使用內嵌繫結觀察表單的驗證狀態。

1
2
3
4
<p>
Form Value: {{ profileForm.valid }} <br>
Form Status: {{ profileForm.status }}
</p>

firstName 未輸入,狀態為 INVALID

輸入後,狀態為 VALID

而我們也可以搭配原生的 HTML5 驗證屬性,可以防止在 Template 檢查完之後表示式再次被修改導致的錯誤。

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

可以做得更好!像是把驗證有沒有通過的狀態繫結在提交按鈕的 disabled 屬性上。

1
<button type="submit" [disabled]="!profileForm.valid">提交</button>

驗證未通過

驗證通過

提交按鈕被禁用了,因為 firstName 控制元件的必填項規則導致了 profileForm 也是無效的。

使用表單陣列管理動態控制元件

FormArray 是 FormGroup 之外的另一個選擇,用於管理任意數量的匿名控制元件。

像 FormGroup 實例一樣,可以在 FormArray 中動態插入和移除控制元件,並且 FormArray 實例的值和驗證狀態也是根據它的子控制元件計算得來的。

FormArray 與 FormGroup 差別在於不需要為每個控制元件定義一個名字作為 key

因此,如果不知道子控制元件的數量,這就是一個很好的選擇。

舉例來說,我們可以加入綽號的部分,因為我們不知道綽號會有幾個。

匯入 FormArray

從 @angular/form 中匯入 FormArray,以使用它的型別資訊。

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

定義 FormArray

可以透過把一組(從零項到多項)控制元件定義在一個陣列中藉以初始化一個 FormArray。

為 profileForm 新增一個 alias 屬性,把它定義為 FormArray 型別。

使用 FormBuilder.array() 方法來定義該陣列,並用 FormBuilder.control() 方法來往該陣列中新增一個初始控制元件。

1
2
3
4
5
6
7
8
9
10
11
12
13
profileForm = this.fb.group({
firstName: ['', Validators.required],
lastName: [''],
address: this.fb.group({
street: [''],
city: [''],
state: [''],
zip: ['']
}),
alias: this.fb.array([
this.fb.control('')
])
});

現在 FormGroup 中的這個 alias 控制元件現在管理著一個控制元件,將來還可以動態新增多個。

訪問 FormArray 控制元件

相對於重復使用 profileForm.get() 方法獲取每個例項的方式, getter 可以讓你輕鬆訪問 FormArray 實例中的綽號。

FormArray 實例用一個陣列來代表未定數量的控制元件。

透過 getter 來訪問控制元件很方便,這種方法還能很容易地重複處理更多控制元件。

使用 getter 語法建立類屬性 aliases,以從父表單組中接收表示 aliases 的 FormArray 控制元件。

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

因為 return 的控制元件的型別是 AbstractControl,所以要為該方法提供一個顯式的型別宣告來訪問 FormArray 特有的語法。

宣告 addAlias 方法來把一個控制元件動態插入到 aliases 的 FormArray 中:

  • 用 FormArray.push() 方法把該控制元件新增為陣列中的新元素
1
2
3
addAlias() {
this.aliases.push(this.fb.control(''));
}

在 Template 中,這些控制元件會被迭代,把每個控制元件都顯示為一個獨立的輸入框。

在 Template 中顯示 FormArray

使用 formArrayName 在這個 FormArray 例項和範本之間建立繫結。

1
2
3
4
5
6
7
8
9
<div formArrayName="aliases">
<h3>Aliases</h3> <button (click)="addAlias()">新增綽號</button>
<div *ngFor="let item of aliases.controls; let i=index">
<label>
綽號:
<input type="text" [formControlName]="i">
</label>
</div>
</div>

使用 ngFor 指令對 aliases FormArray 提供的每個 FormControl 進行迭代。

因為 FormArray 中的元素是匿名的,所以要把索引號賦值給 i 變數,並且把它傳給每個控制元件的 formControlName 輸入屬性。

每當新的 alias 加進來時,FormArray 的實例就會基於這個索引號提供它的控制元件。

至此,我們完成了響應式表單的基礎範例練習。

0%