angular 自定义验证器

先说"响应式表单"的用法

新建的文件,用来写自定义验证器

import { AbstractControl } from "@angular/forms";
//control是我们要验证的表单控件, export function beginWith(control: AbstractControl) { const result = /^13/.test(control.value); return result ? null : {'beginWith': {value: control.value}};
}

上面返回语句中的"beginWith"是我们自定义的一个错误类型名,如果被验证的控件不满足我们自定义的这个验证规则, 控件元素的实例对象

在组件类文件中

//要引用自定义验证器的文件,并且引入表单相关类
import { beginWith } from '....../上面定义验证器函数的文件名';
import { FormGroup, FormControl, Validators } from '@angular/forms'; //Validators是ng自带的内置验证器类,里面有一些对html5验证规则的封装方法.
...

//在constructor中定义表单控件实例,并用内置验证器和自定义验证器始初化

this.heroForm = new FormGroup({

phone: new FormControl('蝙蝠侠', [Validators.required, Validators.minLength(4),beginWith])

});

  //一定要加上这个getter属性,这样在模板中才能取到name,下面模板中name.errors中的name就是通过这个getter方法取到的

get phone() { return this.heroForm.get('name'); }

在模板中

<form [formGroup]="heroForm" novalidate>
    <input >        
    <div *ngIf="phone.invalid && (phone.dirty || phone.touched)" class="alert alert-danger">
        <div *ngIf="phone.errors.required">
            必填
        </div>
        <div *ngIf="phone.errors.minlength">
            至少4个字母
        </div>
        <div *ngIf="phone.errors.beginWith">
            必须是13
        </div>
    </div>
</form>

上面定义的验证器中的正则表达式是写死了, 可以改造一下, (这也是官方文档的例子https://angular.cn/guide/form-validation#adding-to-reactive-forms):

export function beginWith(regExp: RegExp): ValidatorFn {
  return (control: AbstractControl): {[key: string]: any} | null => {
    const isMatch = regExp.test(control.value);
    return isMatch ? null : {'beginWith': {value: control.value}};
  };
}

之前的写法其实就是直接export(导出)一个验证器函数(ValidatorFn),这个修改的写法就是个工厂函数,即调用它时要传进来一个正则表达式,它会返回一个使用这个正则表达式创建的验证器函数(ValidatorFn),ValidatorFn是一个函数接口,参数为一个表单控件对象,返回值是一个错误信息对象或null(如果没有错误).

相应的组件类中的创建表单对象的代码也要改下:

this.heroForm = new FormGroup({
    name: new FormControl('蜘蛛侠', [Validators.required, Validators.minLength(4), beginWith(/^13/)])
});

"响应式表单"的实现方式需要在表单组件类所在模块( 可能是根模块 )引入ReactiveFormsModule,

import { FormsModule, ReactiveFormsModule } from '@angular/forms';

接下来看下模板驱动表单的实现方式:

用自定义指令的方式将上面写自定义验证器进行包装 : 官方的原文:必须创建一个指令,它会包装这个验证器函数。我们使用 NG_VALIDATORS 令牌来把它作为验证器提供出来. https://angular.cn/guide/form-validation#adding-to-reactive-forms

// 自定义验证器
import { ValidatorFn, AbstractControl, NG_VALIDATORS, Validator } from "@angular/forms";
import { Directive, Input } from "@angular/core";

export function beginWith(nameRe: RegExp): ValidatorFn {
  return (control: AbstractControl): { [key: string]: any } | null => {
    const result = nameRe.test(control.value);
    return result ? null : { 'beginWith': { value: control.value } };
  };
}

@Directive({
  selector: '[phone]', //在模板中的属性指令就要写成 phone="..."
  providers: [{
    provide: NG_VALIDATORS, //这里我还没太搞弄,参考下官方文档吧
    useExisting: ForbiddenValidatorDirective, //这个属性的意思官方文档上写的我也没太懂,总之呢要和下面类名一致
    multi: true //官方文档是说要想让一个控件同时支持多个验证器就要写 multi: true
  }]
})
export class ForbiddenValidatorDirective implements Validator {
  @Input('phone') regExp_str: string; //通过模板上的属性指令取到的字符串值

  validate(control: AbstractControl): { [key: string]: any } | null {
    const regExp = new RegExp(this.regExp_str, 'i'); //创建正则表达式对象
    const validatFn = beginWith(regExp);  //直接调用上面定义的工厂函数, 它返回的是一个验证器函数
    return this.regExp_str ? validatFn(control) : null; //然后调用这个验证器, control就是当前指令所属的表单控件
  }
}

修改组件类的代码:

...
export class MyApp {
  ...
  //"模板驱动式表单"的数据就直接用类属性定义
  hero = {
    phone: '13333333333'
  };

  constructor() {
//      this.heroForm = new FormGroup({
//        name: new FormControl('王宁', [Validators.required, Validators.minLength(4), beginWith(/^123/)])
//      });
  }

  //get name() { return this.heroForm.get('name'); }
}

然后 修改一下模板

<form>
    <input >

    <div *ngIf="phone.invalid && (phone.dirty || phone.touched)" >
        <div *ngIf="phone.errors.required">
            必填
        </div>
        <div *ngIf="phone.errors.minlength">
            至少11位数
        </div>
        <div *ngIf="phone.errors.beginWith">
            手机号必须是13开头
        </div>
    </div>
</form>

"模板驱动式表单"中, 需要定义模板变量 #xxx="ngModel" (就是上面的#phone), 下面的错误提示DIV中用的phone.invalid 和 phone.dirty 等,其中的phone就是这个模板变量xxx,即指向被绑定的数据模型,这里是hero.phone