大家在日常生活中,應該看過滿多表單的某個欄位會隨著另個欄位的改變,而造成該欄位的驗證邏輯需要改變的情況吧?
舉例來說,可能會有個欄位叫做聯絡資訊,使用者可以選擇要填入手機號碼或者是 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>
|
畫面看起來會像這樣:
雖然聯絡資訊是一個欄位,但其實我們需要兩個 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>
|
結果:
看起來已經有正確跟畫面上的元素綁定了,那接下來要怎麼做才好呢?
valueChanges
FormControl 的父類別 AbstractControl 有個屬性叫做 valueChanges ,它是一個 Observable 。
我們可以透過訂閱某個 AbstractControl 的 valueChanges 這個 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 }); }
|
上述程式碼中有以下三個要點:
建立 FormControl
的時候可以藉由 this.formBuilder.control()
的方式建立,也可以直接使用 new FormControl()
建立,這點在前面的文章已經有提過,不過我在這邊再提醒大家一次。
setValidators()
執行完後,記得一定要使用 updateValueAndValidity()
來更新當前欄位的驗證,不然就要等到該欄位的值有改變時才會以新的驗證器來驗證。
由於 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 格式錯誤'; } }
|
如此一來,我們就完成這個欄位的功能囉!
結果:
本日小結
今天的重點是學會如何使用 valueChanges 來動態調整相關欄位的驗證邏輯。
雖然是 Observable 是 RxJS 的東西,但今天並沒有太艱難或太複雜的運用,使用上的感覺會跟使用 Promise 的感覺類似,不過我個人認為 RxJS 好玩且強大許多。
關於 RxJS ,如果大家想知道更多資訊,我推薦大家去看 Mike 的打通 RxJS 任督二脈系列文,或者是直接買實體書也行。
雖然今天的實作已經完成了,但因為有調整程式碼的關係,測試程式碼其實也需要相應的調整才不會出錯,此部份就交給大家實作我就不再用篇幅分享實作囉!
今天的程式碼會放在 Github - Branch: day23 上供大家參考,建議大家在看我的實作之前,先按照需求規格自己做一遍,之後再跟我的對照,看看自己的實作跟我的實作不同的地方在哪裡、有什麼好處與壞處,如此反覆咀嚼消化後,我相信你一定可以進步地非常快!
如果有任何的問題或是回饋,還請麻煩留言給我讓我知道!