之前在开发表单的时候,我们都是使用 Angular 所提供的验证器来验证表单栏位里的值是否符合我们的需求。
虽然 Angular 已经这麽贴心地提供了这麽多验证了,但每个国家、地区的人文风土民情都不同,还有太多太多需要我们自己自订规则才能符合需求的情况。
因此,今天我们就一起来看看要怎麽自订验证器吧!
既然要自订验证器,就不能不知道验证器的型别与其定义。
其实之前在第三天的文章:Reactive Forms 实作 - 以登入为例 里就有提到验证器的型别。
验证器的型别是 ValidatorFn
,其原始码定义如下:
interface ValidatorFn {
(control: AbstractControl): ValidationErrors | null
}
从定义中我们可以知道,验证器其实就只是一个函式,该函式会传入一个型别为 AbstractControl
的参数来让我们在函式中判定该栏位的值是否符合我们的需求。
如果验证结果符合需求,那就回传 null
,代表没有任何的错误;如果验证结果不符合需求那就回传一个型别为 ValidationErrors
的错误。
那 ValidationErrors
又是什麽呢?
ValidationErrors
之前最早是在第二天的文章: Template Driven Forms 实作 - 以登入为例 里登场。
其原始码定义如下:
type ValidationErrors = {
[key: string]: any;
};
没错,你没看错,就是这麽简单!
从定义上看起来,基本上只要是个物件,就符合该型别的要求,而这也是因为满足客制的条件,让使用 Angular 的开发者有程度的规范但拥有尽可能大的弹性。
不过虽然大家可以随意自订,但我非常建议大家在自订的时候可以参考官方的验证器。
举例来说,官方的 Validators.required
验证器在验证有误时,会回传的 ValidationErrors
是:
{ required: true }
而 Validators.pattern
验证器在验证有误时,会回传的 ValidationErrors
是:
{
actualValue: 'xxx',
requiredPattern: 'xxx'
}
Validators.minlength
跟 Validators.minlength
验证器在验证有误时,则会回传:
{
actualLength: 1,
requiredLength: 2
}
我们从中不难发现官方会在 ValidationErrors
中,回传该栏位当前的状态以及需求的状态;而物件的属性名称也会按照 actual
加上 XXXX
以及 required
加上 XXXX
的方式来命名。
虽然具体上还是要看实际需求,但我个人觉得我们自己在自订验证器的 ValidationErrors
时,也可以照着这个规则来处理。
一方面,整个系统会比较一致;另一方面,也不需要多写太多额外的程序来处理我们自订的错误。
举个例子,如果我们想自订一个栏位的值只能是 Leo
的验证器,其程序码可能会像是这样:
export leoValidator: ValidatorFn = (control: AbstractControl) => {
const isLeo = control.value === 'Leo';
if (isLeo) {
return null
}
return {
actualValue: control.value,
requiredValue: 'Leo'
};
};
如此我们就在需要用到它时,直接像这样使用即可:
new FormControl('', leoValidator);
又或者是弹性一点,让使用它的人来决定该栏位的值只能是什麽,其程序码应该会像是这样:
export function nameValidator(name: string): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
if (control.value === name) {
return null
}
return {
actualValue: control.value,
requiredValue: name
};
};
}
然後就可以像这样使用:
new FormControl('', nameValidator('Leo'));
不过, ValidatorFn
是用同步的方式来执行验证,万一遇到需要非同步验证的情况要怎麽办?
大家应该都满喜欢玩游戏的吧?!
谜之音:不要自己爱玩就认为别人都爱玩
绝大多数的游戏,尤其是线上游戏,在取名时不能够取跟别人相同的名字,而当取到跟别人一样的名字时,系统会提示「此名称已被使用」之类的错误讯息。
面对这种应用场景,相信大部分的朋友可能会是使用 valueChanges
来订阅栏位的变化事件,当使用者输入名称时,会呼叫 API 让後端来帮忙验证该名字是否已被使用,然後再根据回传结果来决定是否显示错误讯息。
毕竟前端不可能事先取得几千、几万甚至是几十万、几百万的名字再一一比对吧?
不过如果有这样的应用场景,我个人觉得还满适合使用非同步验证器来处理的。
顺带一提,这只是我个人举例,不代表真实应用情况。
interface AsyncValidator extends Validator {
validate(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null>
// inherited from forms/Validator
validate(control: AbstractControl): ValidationErrors | null
registerOnValidatorChange(fn: () => void)?: void
}
从上述定义可以看出,跟同步的验证器所不一样的是,我们在自订非同步的验证器时,不是直接制作一个符合 AsyncValidatorFn
定义的函式
而是要用一个可被注入的 Class 来实作 AsyncValidator
这个介面。
就像下面这个官网的范例一样:
@Injectable({ providedIn: 'root' })
export class UniqueAlterEgoValidator implements AsyncValidator {
constructor(private heroesService: HeroesService) {}
validate(
ctrl: AbstractControl
): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
return this.heroesService.isAlterEgoTaken(ctrl.value).pipe(
map(isTaken => (isTaken ? { uniqueAlterEgo: true } : null)),
catchError(() => of(null))
);
}
}
为我们栏位设定非同步验证器的方式也非常地简单。
以 FormControl
来说, 我们可以用这样子的方式来设定:
new FormControl('', [/* 一般验证器 */], [/* 非同步验证器 */]);
简单来说,不论是用 Reactive Forms 的哪种方式建立栏位,非同步验证器都是放在一般验证器後面就对了!
希望透过今天的分享,能让大家可以初步掌握自订验证器的技巧。虽然在实务上,大家不一定遇的到需要使用非同步验证器的场景,但如果真的有需要用到又忘记怎麽做时,至少有这篇文章在,随时都可以回来查询。
此外,虽然没有分享自订 Template Driven Forms 验证器的方式,但大家可以自行参考官方的 Form Validation - Adding custom validators to template-driven forms 文件。
而在非同步验证器的部份,官网也有提到一些优化非同步验证器的技巧,大家可以参考官方的 Form Validation - Optimizing performance of async validators 文件。
以上,就是今天的文章,如果有任何的问题或是回馈,还请麻烦留言给我让我知道,感激不尽!
2. VGG 实作(tensorflow) 2.1 南无观世"import"啥?...
今日目标 基本的数学函式库(向量与阵列) 要多少才够 从另一个角度看,我认为游戏中的从小小的让角色移...
Ngrok运行原理 其实ngrok有客户端ngrok和服务端ngrokd,在用户客户端发起请求时,就...
这次我们要来学习资料型态在程序中的大小,亦即调查其所占的空间。 我列出一些常用的data type~...
我们飞快的结束惹html ,css,欢迎进入到下一个阶段JavaScrip。JS的助教很严,学习之前...