同步校验没什么可说的,在做异步校验的时候着实吃了不少苦,百度搜出来几乎全是复制的同一份教程连编译都过不去。自己比着官网例子写一份又因为对Rxjs不熟悉干的磕磕绊绊,头发大把的掉。这里把代码贴出来,同时也记录一下遇到的知识点,以备回顾。
注:本文以响应式表单为基础。
FormBuilder
FormBuilder其实就是一个可以快速生成表单控件的工具,导入该类之后可以通过this.fb.group
方法快速创建一个FormGroup,例如
export class ProfileEditorComponent {
profileForm = this.fb.group({
firstName: [''],
lastName: [''],
address: this.fb.group({
street: [''],
city: [''],
state: [''],
zip: ['']
}),
});
}
其中每一项都是一个FormControl,需要在html中使用formControlName
属性将其与之绑定。每个控件名对应的值都是一个数组,数组由三个参数组成,例如
email: [null, [Validators.required, this.emailRegxValidator()], this.emailUniqueValidator()]
数组中第一项表示字段的默认值,null表示没有
数组第二项为一个数组,其中填写所需同步校验器的方法名,即不需要后台校验的校验方法
数组第三项仍为一个数组,其中填写所需异步校验器的方法名
Rxjs操作符
Angular其实就是Rxjs和TypeScript的大集合,如果对Rxjs操作符完全不理解的话基本上是开发不下去的,但是Rxjs上手难度还是很高的(至少对我个人是这样)。在深入浅出Rxjs一书中是这么描绘Rxjs学习曲线的
太多的操作符现在我也不是很清楚,简要记录一下异步校验所用到的。
pipe 这个还是相对好理解的,就是字面含义管道。pipe指令可以将一系列操作符串接起来。在管道中,上一个操作符流出的数据流会流入下一个操作符,就像流水一样。
map map的作用类似于执行一个函数操作,在map操作符中可以将元数据按照一定的逻辑进行转化,其实就是输入一个数据,然后调用一个函数得到运算之后的数据。
switchMap 这个操作符什么意思至今未懂,只知道他可以调用一个服务然后把数据传进去。
校验器
回过头来看一下表单校验的校验器。校验器共有两种,同步校验和异步校验,验证器函数接受一个control,然后返回一组错误对象(验证不通过)或 null(验证通过),当未返回任何内容时表示未开始校验。
异步校验器要求返回Promise或Observable,同时返回的可观察对象必须是有限的,也就是说,它必须在某个时间点结束(complete)。要把无尽的可观察对象转换成有限的,可以使用 first、last、take 或 takeUntil 等过滤型管道对其进行处理。
验证错误是一个对象,对象结构唯一的要求是key必须为字符串,值可以为any类型,例如你可以返回一个{duplicate: true}
,表示当前control的duplicate校验未通过。
值得注意的是,出于性能考虑,异步校验器会在所有同步校验器完成之后才会触发,在此之前异步校验器会处于正确状态。
异步验证开始时,表单将进入Pending状态,当验证结果返回之后才会变成valid或invalid。在判断表单状态的时候需要注意一下异步问题。当判断为pending时进入一个定时器循环判断直到不为pending为止。
实例
有了上面的基础写一个异步校验器应该就没什么大问题了。不过http的防抖需要考虑进去,不然用户每输入一个字符就要触发一次后台校验成本太高了。
userNameUniqueValidator() {
return (control: FormControl): any => {
//进入管道进行串行操作
//valueChanges表示字段值变更才触发操作
return control.valueChanges.pipe(
//同valueChanges,不写也可
distinctUntilChanged(),
//防抖时间,单位毫秒
debounceTime(400),
//调用服务,参数可写可不写,如果写的话变成如下形式
//switchMap((val) => this.registerService.isUserNameExist(val))
switchMap(() => this.registerService.isUserNameExist(control.value)),
//对返回值进行处理,null表示正确,对象表示错误
map(res => res.code == "200" ? null : {duplicate: true}),
//每次验证的结果是唯一的,截断流
first()
);
}
}