Angular 表單應用及驗證
Angular 表單應用
概述
在 Angular framework 中, Form 實作區分為分為兩種Module:
- ReactiveFormsModule : Model driven form (Reactive form)
- FormsModule : Template driven form
功能上,兩者均可以達成表單控制項操作、驗證、事件觸發等等;而 Model driven form 又可稱為 Reactive form,是由於 Model driven form 是以 reactive、意即observable模式設計,控制項的事件以資料流的方式建構表單,因此這些資料是即時同步被更新的。
Template driven form 是以 ngModel 和控制項進行two-way-binding方式驅動( 詳見:Angular官網 ),適用於快速開發單純的表單欄位及驗證,但由於包含驗證的程式碼均位於html,控制項的事件也並非同步,可能導致後續測試的困擾,較不適用於複雜結構的表單。
Angular Form 基本類別介紹
- AbstractControl: FormControl、FormGroup、FormArray這三個實例表單類的抽象基類,提供了subclasses的通用行為以及屬性,例如observable。
- FormControl:在單個表單元件中檢查值並驗證狀態(比如input、select等等)。
- FormGroup:包含AbstractControl索引或名稱對應實體的集合(controls屬性),通常一個form表單會綁定至少一個FormGroup。
- FormArray:用索引的方式去追蹤檢查表單的驗證狀態。
建構 Reactive Form
我們可以利用建構函式,或類別 FormBuilder 的.group( )、array( )方法初始化出FormGroup實體。
constructor(private fb: FormBuilder) { }
formModel: FormGroup;
ngOnInit() {
// 建構函式
this.formModel = new FormGroup(
{ 'name' : new FormControl( '', Validators.required) }
);
// FormBuilder
this.formModel = this.fb.group({name: ['', Validators.required]})
}
而在.html中,則僅需綁定FormGroup以及formControl ( 亦有其他相似語法,如:[FormControl]="formModel.get('name')" )
<form [formGroup]="formModel">
<input type="text" formControlName="name"/>
</form>
在上面的程式碼中,可以看到Validators.required,在初始化時也加到FormControl中,這邊的required是Angular提供的靜態方法,將HTML5 Form Validation,以介面 ValidatorFn 的方式實作,讓我們能將邏輯都集中在.ts當中處理,保持html簡單、僅提供畫面及綁定;另外,我們亦可以實作 ValidatorFn介面來客製化驗證邏輯。
(以下為實作 Template Driven Form 的 html程式碼,驗證邏輯required直接以Directive形式綁定 html。)
<form>
<input type="text" name="name" [(ngModel)]="data.name" required />
</form>
Reactive Form、 Template Driven Form 範例程式 ( Live Example / GitHub )
範例中,主要有兩個頁面:
- Abstract Controls : 呈現AbstractCotrol三種子類別,並應用 FormArray 達成動態生成欄位的效果,當FormGroup物件改變,屬性綁定的<form>亦隨之改變。
- Form Validation : 呈現 Reactive form 及 Template driven form 表單的驗證比較;其中實作了 ValidatorFn 讓 Reactive form 使用,而為了讓 Template driven form 也能使用,實作Validator介面用Directive的方式叫用,請見節錄程式碼 :
/** age should not less than 0 or larger than 120 */
export const ageValidator: ValidatorFn = (control: FormControl): ValidationErrors | null => {
const age = +control.value;
if (!control.value || control.value === '') return null;
return !(age && age > 0 && age < 120) ? { 'ageValidator': true } : null;
};
@Directive({
selector: '[appValidAge]',
providers: [{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => ValidAgeDirective),
multi: true
}]
})
@Injectable({ providedIn: 'root' })
export class ValidAgeDirective implements Validator {
validate(control: AbstractControl): ValidationErrors {
return ageValidator(control)
}
}
Reactive Form、 Template Driven Form 比較
| Reactive Form | Template Driven Form | |
|---|---|---|
| 建構方式 | 在component.ts中建立,較為明確、集中,和html耦合性較低。 | 在html中以ngModel等Directive方式建立,開發快速。 |
| 綁定之資料模型 | 可為樹狀結構 | 非樹狀 |
| 表單驗證 | 實作ValidationFn | Directive |
| 表單控制項變動 | 同步,valueChanges 傳出observale streams | 非同步,控制項事件觸發ngModelChange |
| 適用狀況 | 巢狀資料模型、需要動態處理控制項及驗證 | 單層資料模型、無其他相依表單 |
後記
本文範例中,主要透過 Angular Material form-field 來呈現控制項錯誤效果,而為了跨控制項的錯誤效果,另實作了Material Input 的 ErrorStateMatcher介面,詳細請見(Material官網: Input ErrorMatcher)。
參考資料:
Reactive Forms (Model-Driven Forms) Claire Chang
alligator.io: Reactive-forms with formArray and dynamic-fields
