本文咱們將介紹 Reactive Form 表單驗證的相關知識,具體內容以下:html
使用內建的驗證規則typescript
動態調整驗證規則優化
自定義驗證器ui
自定義驗證器 (支持參數)this
跨字段驗證spa
Angular 提供了一些內建的 validators,咱們能夠在 Template-Driven 或 Reactive 表單中使用它們。code
目前 Angular 支持的內建 validators 以下:orm
required - 設置表單控件值是非空的。htm
email - 設置表單控件值的格式是 email。對象
minlength - 設置表單控件值的最小長度。
maxlength - 設置表單控件值的最大長度。
pattern - 設置表單控件的值需匹配 pattern 對應的模式。
this.signupForm = this.fb.group({ userName: ['', [Validators.required, Validators.minLength(3)]], email: ['', [Validators.required, Validators.pattern('[a-z0-9._%+_]+@[a-z0-9.-]+')]] });
myControl.setValidators(Validators.required); myControl.setValidators([Validators.required, Validators.maxLength(6)]); myControl.clearValidators(); myControl.updateValueAndValidity();
function myCustomValidator(c: AbstractControl): {[key: string]: boolean} | null { if(somethingIsWrong) { return { 'myvalidator': true}; } return null; }
function myCustomValidator(param: any): ValidatorFn { return (c: AbstractControl): {[key: string]: boolean} | null { if(somethingIsWrong) { return { 'myvalidator': true}; } return null; } }
function emailMatcher(c: AbstractControl) { let emailControl = c.get('email'); let confirmControl = c.get('confirmEmail'); if (emailControl.pristine || confirmControl.pristine) { return null; } return emailControl.value === confirmControl.value ? null : { 'match': true }; }
ngOnInit(): void { this.signupForm = this.fb.group({ userName: ['', [Validators.required, Validators.minLength(6)]], emailGroup: this.fb.group({ email: ['', [Validators.required, Validators.email]], confirmEmail: ['', [Validators.required]], }, { validator: emailMatcher }) });
在介紹表單驗證前,咱們來看一下目前頁面的顯示效果:
表單的內建驗證規則,前面章節已經介紹過了,接下來咱們來介紹在表單中如何 "動態調整驗證規則" 。
爲了演示 "動態調整驗證規則" 的功能,我新增了兩個控件:
radio - 用於讓用戶設置是否開啓手機登陸。
tel - 當用戶開啓手機登陸功能,用於讓用戶輸入手機號碼。
當用戶開啓手機登陸功能,手機號碼對應控件的驗證規則,必須是必填且格式爲合法的手機號碼。當用戶不開啓手機登陸功能時,手機號碼對應控件將不是必填的。
<div class="form-group"> <div class="col-md-offset-1 col-md-8 checkbox"> 開啓手機登陸 <label> <input type="radio" value="1" formControlName="enableMobile"> 是 </label> <label> <input type="radio" value="0" formControlName="enableMobile"> 否 </label> </div> </div>
<div class="form-group" [ngClass]="{'has-error': (mobile.touched || mobile.dirty) && !mobile.valid }"> <label class="col-md-2 control-label" for="mobileId">手機號碼</label> <div class="col-md-8"> <input class="form-control" id="mobileId" type="text" placeholder="請輸入手機號碼" formControlName="mobile"/> <span class="help-block" *ngIf="(mobile.touched || mobile.dirty) && mobile.errors"> <span *ngIf="mobile.errors.required"> 請輸入手機號碼 </span> <span *ngIf="mobile.errors.minlength"> 手機號碼格式不正確 </span> </span> </div> </div>
ngOnInit(): void { ... this.signupForm.get('enableMobile').valueChanges .subscribe(value => this.checkMobile(value)); } checkMobile(enableMobile: string): void { const mobileControl = this.signupForm.get('mobile'); enableMobile === "1" ? mobileControl.setValidators([Validators.required, Validators.pattern('1(3|4|5|7|8)\\d{9}')]) : mobileControl.clearValidators(); mobileControl.updateValueAndValidity(); }
介紹完如何動態調整驗證規則,接下來咱們來介紹如何 "自定義驗證器"。
爲了演示 "自定義驗證器" 的功能,我新增了一個控件:
number - 用於讓用戶設置是年齡信息。
當讓用戶手動輸入年齡信息時,咱們須要設置一個有效的年齡範圍,好比 (18 - 120)。此時咱們就須要經過自定義驗證器來實現上述功能。
<div class="form-group" [ngClass]="{'has-error': (age.touched || age.dirty) && !age.valid }"> <label class="col-md-2 control-label" for="ageId">年齡</label> <div class="col-md-8"> <input class="form-control" id="ageId" type="number" placeholder="請輸入年齡" formControlName="age"/> <span class="help-block" *ngIf="(age.touched || age.dirty) && age.errors"> <span *ngIf="age.errors.range"> 輸入年齡不合法 </span> </span> </div> </div>
function myCustomValidator(c: AbstractControl): {[key: string]: boolean} | null { if(somethingIsWrong) { return { 'myvalidator': true}; } return null; }
function ageValidator(c: AbstractControl): { [key: string]: any } | null { let age = c.value; if (age && (isNaN(age) || age < 20 || age > 120)) { return { 'range': true, min: 20, max: 120 }; } return null; }
ngOnInit(): void { this.signupForm = this.fb.group({ // ... age: ['', ageValidator] }); }
咱們的 ageValidator
自定義驗證器,雖然已經實現了。細心的讀者應該會發現,在 ageValidator
驗證器內部,咱們寫死了年齡的邊界值 (最小值與最大值)。理想的狀況下,應該可以讓用戶自行設定邊界值。所以接下來,咱們來優化一下 ageValidator
驗證器。
function myCustomValidator(param: any): ValidatorFn { return (c: AbstractControl): {[key: string]: boolean} | null { if(somethingIsWrong) { return { 'myvalidator': true}; } return null; } }
function ageRange(min: number, max: number): ValidatorFn { return (c: AbstractControl): { [key: string]: any } | null => { let age = c.value; if (age && (isNaN(age) || age < min || age > max)) { return { 'range': true, min: min, max: max }; } return null; } }
ngOnInit(): void { this.signupForm = this.fb.group({ // ... age: ['', ageRange(20, 120)] }); }
介紹完如何自定義驗證器,接下來咱們來介紹如何實現 "跨字段驗證" 的功能。
在平常生活中,在註冊表單中,常常要讓用戶再次輸入一樣的字段值,好比登陸密碼或郵箱地址的值。針對這種場景,咱們就須要驗證兩個控件的輸入值是否一致,這時咱們就要引入跨字段驗證的功能。
爲了演示 "跨字段驗證" 的功能,我新增了一個控件:
email - 用於讓用戶確認輸入的郵箱地址
<label class="col-md-2 control-label" for="emailId">確認郵箱</label> <div class="col-md-8"> <input class="form-control" id="confirmEmailId" type="email" placeholder="請再次輸入郵箱地址" formControlName="confirmEmail"/> <span class="help-block" *ngIf="(confirmEmail.touched || confirmEmail.dirty)"> <span *ngIf="confirmEmail.errors?.required"> 請輸入郵箱地址 </span> <span *ngIf="!confirmEmail.errors?.required && emailGroup.errors?.match"> 兩次輸入的郵箱地址不一致 </span> </span> </div>
function emailMatcher(c: AbstractControl) { let emailControl = c.get('email'); let confirmControl = c.get('confirmEmail'); if (emailControl.pristine || confirmControl.pristine) { return null; } return emailControl.value === confirmControl.value ? null : { 'match': true }; }
ngOnInit(): void { this.signupForm = this.fb.group({ userName: ['', [Validators.required, Validators.minLength(6)]], emailGroup: this.fb.group({ email: ['', [Validators.required, Validators.email]], confirmEmail: ['', [Validators.required]], }, { validator: emailMatcher }), // ... });
<div class="form-group" formGroupName="emailGroup" [ngClass]="{'has-error': emailGroup.errors }"> <label class="col-md-2 control-label" for="emailId">郵箱</label> <div class="col-md-8"> <input class="form-control" id="emailId" type="email" placeholder="請輸入郵箱地址" formControlName="email"/> <span class="help-block" *ngIf="(email.touched || email.dirty) && email.errors"> <span *ngIf="email.errors.required"> 請輸入郵箱地址 </span> <span *ngIf="!email.errors.required && email.errors.email"> 請輸入有效的郵箱地址 </span> </span> </div> <!--其他部分:請參考"新增email控件"的內容--> </div>
上面代碼中,有如下幾個問題須要注意:
Form Group 是能夠嵌套使用的。
this.signupForm = this.fb.group({ userName: ['', [Validators.required, Validators.minLength(6)]], emailGroup: this.fb.group({ email: ['', [Validators.required, Validators.email]], confirmEmail: ['', [Validators.required]], }, { validator: emailMatcher })
咱們經過 formGroupName="groupName"
語法來綁定內嵌的 Form Group。
<div class="form-group" formGroupName="emailGroup" [ngClass]="{'has-error': emailGroup.errors }">
郵箱不匹配的信息是存在 emailGroup
對象的 errors 屬性中,而不是存在 confirmEmail
對象的 errors
屬性中。
<span *ngIf="!confirmEmail.errors?.required && emailGroup.errors?.match"> 兩次輸入的郵箱地址不一致 </span>
export class AppComponent { constructor(private fb: FormBuilder) { this.form = fb.group({ name: 'semlinker', age: 31 }); this.form.valueChanges.subscribe(data => { console.log('Form changes', data) }); } }
模板
<form #myForm="ngForm" (ngSubmit)="onSubmit()"> <input type="text" name="name" class="form-control" required [(ngModel)]="user.name"> <input type="number" name="age" class="form-control" required [(ngModel)]="user.age"> </form>
組件類
class AppComponent implements AfterViewInit { @ViewChild('myForm') form; ngAfterViewInit() { this.form.control.valueChanges .debounceTime(500) .subscribe(values => this.doSomething(values)); } }