Angular 深入淺出三十天:表單與測試 Day23 - Reactive Forms 進階技巧 - 欄位連動檢核邏輯

Day23

大家在日常生活中,應該看過滿多表單的某個欄位會隨著另個欄位的改變,而造成該欄位的驗證邏輯需要改變的情況吧?

舉例來說,可能會有個欄位叫做聯絡資訊,使用者可以選擇要填入手機號碼或者是 E-mail ,該欄位再根據使用所選擇的類型來檢核該欄位的值。

今天,我們就來用 Reactive Forms 實作這個欄位,而這個欄位我會實作在我們的被保人表單上,各位就隨意吧!

如果已經忘記被保人表單長怎麼樣的話,可以先回頭複習一下第十一天的文章:Reactive Forms 實作 - 動態表單初體驗

實作開始

首先,我們需要在原本的被保人表單裡新增一個欄位:聯絡資訊。

HTML 的部份大概會長這樣:

1
2
3
4
5
6
7
8
9
10
11
<p>
<label>聯絡資訊:</label>
</p>
<p>
<select>
<option value="">請選擇</option>
<option value="mobile">手機</option>
<option value="email">E-Mail</option>
</select>
<input type="text">
</p>

畫面看起來會像這樣:

Insured View

雖然聯絡資訊是一個欄位,但其實我們需要兩個 FormControl ,一個給下拉選單,一個給實際填值的 input 元素。

因此,我們要在原本的 createInsuredFormGroup 裡多加兩個欄位,像是這樣:

1
2
3
4
5
6
7
8
9
10
11
12
private createInsuredFormGroup(): FormGroup {
return this.formBuilder.group({
name: [
'',
[Validators.required, Validators.minLength(2), Validators.maxLength(10)]
],
gender: ['', Validators.required],
age: ['', Validators.required],
contactInfoType: ['', Validators.required],
contactInfo: ['', Validators.required]
});
}

然後將剛剛新增的欄位與畫面的元素綁定:

1
2
3
4
5
6
7
8
<p>
<select formControlName="contactInfoType">
<option value="">請選擇</option>
<option value="mobile">手機</option>
<option value="email">E-Mail</option>
</select>
<input type="text" formControlName="contactInfo">
</p>

接著我們透過把資料印在畫面上的方式來檢查是否已正確綁定,像這樣:

1
<pre>{{ formGroup?.getRawValue() | json }}</pre>

結果:

Insured View

看起來已經有正確跟畫面上的元素綁定了,那接下來要怎麼做才好呢?

valueChanges

FormControl 的父類別 AbstractControl 有個屬性叫做 valueChanges ,它是一個 Observable

我們可以透過訂閱某個 AbstractControlvalueChanges 這個 Observable 來知道該欄位是否已經發生變化,並且做出相應的處理。

因此,我們可以這樣調整 createInsuredFormGroup 裡的實作:

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
private createInsuredFormGroup(): FormGroup {
const contactInfoTypeControl = this.formBuilder.control('', Validators.required);
const contactInfoControl = this.formBuilder.control('', Validators.required);
contactInfoTypeControl.valueChanges.subscribe((value) => {
switch (value) {
case 'mobile':
contactInfoControl.setValidators([Validators.required, Validators.pattern(/$09\d{8}^/)]);
break;
case 'email':
contactInfoControl.setValidators([Validators.required, Validators.email]);
break;
default:
contactInfoControl.setValidators([Validators.required]);
break;
}
contactInfoControl.updateValueAndValidity();
});

return this.formBuilder.group({
name: [
'',
[Validators.required, Validators.minLength(2), Validators.maxLength(10)]
],
gender: ['', Validators.required],
age: ['', Validators.required],
contactInfoType: contactInfoTypeControl,
contactInfo: contactInfoControl
});
}

上述程式碼中有以下三個要點:

  1. 建立 FormControl 的時候可以藉由 this.formBuilder.control() 的方式建立,也可以直接使用 new FormControl() 建立,這點在前面的文章已經有提過,不過我在這邊再提醒大家一次。

  2. setValidators() 執行完後,記得一定要使用 updateValueAndValidity() 來更新當前欄位的驗證,不然就要等到該欄位的值有改變時才會以新的驗證器來驗證。

  3. 由於 contactInfoType 允許使用者選擇 請選擇 的選項,因此記得在 default 的區塊裡,將 Validators.required 給加回去。

這邊改好之後,我們也順便調整一下 getErrorMessage 的實作,讓使用者可以知道該欄位的驗證有誤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
getErrorMessage(key: string, index: number): string {
const formGroup = this.formArray.controls[index];
const formControl = formGroup.get(key);
let errorMessage: string;
if (!formControl || !formControl.errors || formControl.pristine) {
errorMessage = '';
} else if (formControl.errors.required) {
errorMessage = '此欄位必填';
} else if (formControl.errors.minlength) {
errorMessage = '姓名至少需兩個字以上';
} else if (formControl.errors.maxlength) {
errorMessage = '姓名至多只能輸入十個字';

// 增加以下兩個判斷
} else if (formControl.errors.pattern) {
errorMessage = '手機號碼格式錯誤';
} else if (formControl.errors.email) {
errorMessage = 'E-mail 格式錯誤';
}

return errorMessage!;
}

這邊要提醒大家的是,由於驗證 E-mail 格式的方式我今天是用 Validators.email 的驗證器來驗,不是之前的 Validators.pattern() ,所以我可以直接用 formControl.errors.email 來判斷。

如果實作時,手機號碼跟 E-mail 都是用 Validators.pattern() 的驗證器來驗的話,就需要進一步去比對 formControl.errors.pattern 裡的 Regular Expression 來分辨究竟是手機號碼的格式錯誤還是 E-mail 的格式錯誤了。

像是這樣:

1
2
3
4
5
6
7
8
} else if (formControl.errors.pattern) {
const requiredPattern = formControl.errors.pattern.requiredPattern;
if (requiredPattern === '/A Regular Expression/') {
errorMessage = '手機號碼格式錯誤';
} else if (requiredPattern === '/B Regular Expression/') {
errorMessage = 'E-mail 格式錯誤';
}
}

如此一來,我們就完成這個欄位的功能囉!

結果:

Insured View

本日小結

今天的重點是學會如何使用 valueChanges 來動態調整相關欄位的驗證邏輯。

雖然是 Observable 是 RxJS 的東西,但今天並沒有太艱難或太複雜的運用,使用上的感覺會跟使用 Promise 的感覺類似,不過我個人認為 RxJS 好玩且強大許多。

關於 RxJS ,如果大家想知道更多資訊,我推薦大家去看 Mike 的打通 RxJS 任督二脈系列文,或者是直接買實體書也行。

雖然今天的實作已經完成了,但因為有調整程式碼的關係,測試程式碼其實也需要相應的調整才不會出錯,此部份就交給大家實作我就不再用篇幅分享實作囉!

今天的程式碼會放在 Github - Branch: day23 上供大家參考,建議大家在看我的實作之前,先按照需求規格自己做一遍,之後再跟我的對照,看看自己的實作跟我的實作不同的地方在哪裡、有什麼好處與壞處,如此反覆咀嚼消化後,我相信你一定可以進步地非常快!

如果有任何的問題或是回饋,還請麻煩留言給我讓我知道!

評論

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×