Angular Reactive Form 表單驗證

本文咱們將介紹 Reactive Form 表單驗證的相關知識,具體內容以下:html

  • 使用內建的驗證規則typescript

  • 動態調整驗證規則優化

  • 自定義驗證器ui

  • 自定義驗證器 (支持參數)this

  • 跨字段驗證spa

基礎知識

內建驗證規則

Angular 提供了一些內建的 validators,咱們能夠在 Template-DrivenReactive 表單中使用它們。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;
  }
}

跨字段驗證

emailMatcher

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 };
}

emailGroup

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 })
});

在介紹表單驗證前,咱們來看一下目前頁面的顯示效果:

base-template-driven-form

表單驗證

表單的內建驗證規則,前面章節已經介紹過了,接下來咱們來介紹在表單中如何 "動態調整驗證規則"

動態調整驗證規則

爲了演示 "動態調整驗證規則" 的功能,我新增了兩個控件:

  • radio - 用於讓用戶設置是否開啓手機登陸。

  • tel - 當用戶開啓手機登陸功能,用於讓用戶輸入手機號碼。

當用戶開啓手機登陸功能,手機號碼對應控件的驗證規則,必須是必填且格式爲合法的手機號碼。當用戶不開啓手機登陸功能時,手機號碼對應控件將不是必填的。

新增 radio 控件

<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>

新增 tel 控件

<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)。此時咱們就須要經過自定義驗證器來實現上述功能。

新增 number 控件

<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;
}

新增 ageValidator 驗證器

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;
}

使用 ageValidator 驗證器

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;
  }
}

新增 ageRange 驗證器工廠

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;
  }
}

使用 ageRange 驗證器工廠

ngOnInit(): void {
  this.signupForm = this.fb.group({
    // ...
    age: ['', ageRange(20, 120)]
  });
}

介紹完如何自定義驗證器,接下來咱們來介紹如何實現 "跨字段驗證" 的功能。

跨字段驗證

在平常生活中,在註冊表單中,常常要讓用戶再次輸入一樣的字段值,好比登陸密碼或郵箱地址的值。針對這種場景,咱們就須要驗證兩個控件的輸入值是否一致,這時咱們就要引入跨字段驗證的功能。

爲了演示 "跨字段驗證" 的功能,我新增了一個控件:

  • email - 用於讓用戶確認輸入的郵箱地址

新增 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>

新增 emailMatcher

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 };
}

新增 emailGroup

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>

我有話說

怎麼會監聽表單值的變化?

Reactive Form

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)
    });
  }
}

Template-driven Form

  • 模板

<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));
  }
}
相關文章
相關標籤/搜索