先说"响应式表单"的用法
新建的文件,用来写自定义验证器
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验证规则的封装方法.
...
在模板中
<form [formGroup]="heroForm" novalidate> <input id="phone" name="phone" formControlName="phone" autocomplete="off"> <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 id="phone" name="phone" required minlength="11" phone="^13" [(ngModel)]="hero.phone" #phone="ngModel" autocomplete="off"> <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