如何使用带异步调用的验证器指令 邮箱异步校验不生效问题
问题表现
自定义邮箱存在性异步校验指令可正常触发后端API请求,但表单中email控件的errors字段始终返回null,校验结果无法在视图层生效。
相关问题代码:
原指令代码
@Directive({ selector: '[emailValidator][formControlName],[emailValidator][formControl],[emailValidator][ngModel]', providers: [ { provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => EmailValidatorDirective), multi: true } ] }) export class EmailValidatorDirective implements Validator { constructor(private _http: HttpService) {} validate( c: AbstractControl ): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> { return this.validateEmail(c.value); } private validateEmail(email: String): Observable<ValidationErrors | null> { return this._http.post('user/validate-email', { email }).pipe( debounceTime(500), distinctUntilChanged(), map((isUsed) => { // null表示无错误,对象表示有错误 return !isUsed ? null : { isTaken: 'Email exists already.' }; }) ); } }
原模板代码
<input id="email" type="email" class="form-control" formControlName="email" emailValidator placeholder="Enter E-mail Address" /> <!-- 始终打印null --> {{ formcontrol['email'].errors | json }}
问题原因
一共3处核心错误:
- 校验器接口实现错误:指令声明的是异步校验器(
NG_ASYNC_VALIDATORS),但类实现的是同步校验器接口Validator,Angular无法正确识别异步校验返回的Observable对象。 - RxJS操作符位置错误:
debounceTime、distinctUntilChanged是针对连续值流做防抖、去重的操作符,原代码将其放在HTTP请求的pipe后置位,相当于每次值变更都会直接发起HTTP请求,防抖完全失效,且多请求并行时会导致结果回写时序错乱。 - 异步校验流未结束:Angular异步校验器要求返回的Observable必须触发
complete通知,才会将校验结果写入控件的errors字段,原代码没有做流收尾处理,会被判定为校验中,结果不会生效。
修复后代码
import { Directive, forwardRef } from '@angular/core'; import { AbstractControl, AsyncValidator, NG_ASYNC_VALIDATORS, ValidationErrors } from '@angular/forms'; import { Observable, of, timer } from 'rxjs'; import { distinctUntilChanged, map, switchMap, take } from 'rxjs/operators'; import { HttpService } from './对应http服务的引入路径'; @Directive({ selector: '[emailValidator][formControlName],[emailValidator][formControl],[emailValidator][ngModel]', providers: [ { provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => EmailValidatorDirective), multi: true } ] }) // 改为实现AsyncValidator异步校验器接口 export class EmailValidatorDirective implements AsyncValidator { constructor(private _http: HttpService) {} validate(c: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> { // 先做防抖,再发起校验请求 return timer(500).pipe( switchMap(() => { // 空值或控件未修改时直接返回无错误 if (!c.value) { return of(null); } return this._http.post('user/validate-email', { email: c.value }).pipe( map((isUsed: boolean) => { return isUsed ? { isTaken: 'Email exists already.' } : null; }) ); }), distinctUntilChanged(), take(1) // 强制流完成,让Angular接收校验结果 ); } }
补充说明
- 修复后不需要修改模板代码,当邮箱已存在时,
formcontrol['email'].errors会正常输出{ "isTaken": "Email exists already." }。 - 用
timer(500)实现输入停顿500ms再发请求的防抖效果,避免用户输入过程中产生大量无效接口调用。
内容的提问来源于stack exchange,提问作者RAHUL KUNDU
相关产品推荐
相关产品推荐

