Angular 5自定义FormGroup异步验证器不更新错误状态求助
解决Angular异步表单验证器不更新错误状态的问题
我来帮你排查这个问题,核心原因是你把异步验证器当成了同步验证器来使用,同时HTML里的错误判断也有小问题,一步步来修正:
问题根源分析
- Angular的
FormControl构造函数中,第二个参数是同步验证器(只能返回同步的ValidationErrors | null),而你写的validateDisplayName返回了Promise,属于异步验证器,必须放在第三个参数的数组里。 - HTML中
hasError是一个方法,不是属性,你原来的dnFormGroup.get('displayName').hasError写法是错误的,应该直接调用hasError('displayNameTaken')。 - 当输入长度不足3时,你返回了
{ displayNameTaken: true },这会导致输入为空或短于3字符时错误提示一直显示,不符合预期,应该返回null或者单独处理长度验证。
修正步骤
1. 调整FormGroup初始化(把异步验证器放在正确位置)
修改component.ts的ngOnInit方法,将异步验证器移到第三个参数的数组中,同时补充官方长度验证器替代HTML原生属性:
import { Validators } from '@angular/forms'; ngOnInit() { this.dnFormGroup = this.formBuilder.group({ displayName: [ '', [Validators.minLength(3), Validators.maxLength(10)], // 同步验证器:处理长度要求 [this.validateDisplayName.bind(this)] // 异步验证器:处理唯一性检查 ] }); }
2. 修正异步验证器函数
调整validateDisplayName,确保长度不足时返回null(避免错误提示误触发),同时明确返回类型:
import { AbstractControl, ValidationErrors } from '@angular/forms'; public validateDisplayName(control: AbstractControl): Promise<ValidationErrors | null> { console.log("RegisterComponent validateDisplayName - ENTRY"); if(control.value.length >= 3) { return this.authService.validateDisplayName(control.value).then( (res) => { console.log("RegisterComponent validateDisplayName - res: " +res); if(res) { console.log("RegisterComponent validateDisplayName - displayName already used"); return { displayNameTaken: true }; } else { console.log("RegisterComponent validateDisplayName - displayName can be used"); return null; } }, (err) => { console.log("RegisterComponent validateDisplayName - err: " +err); return { displayNameTaken: true }; } ); } console.log("RegisterComponent validateDisplayName - ############## EXIT"); return Promise.resolve(null); // 长度不足时返回null,不触发唯一性错误 }
3. 修正HTML错误提示逻辑
修改component.html中的错误判断,正确使用hasError方法,同时区分长度错误和唯一性错误:
<form [formGroup]="dnFormGroup"> <fieldset> <div class="row"> <section class="col col-6"> <label class="input"> <i class="icon-append fa fa-user"></i> <input formControlName="displayName" type="text" placeholder="Display name"> <b class="tooltip tooltip-top-right"> <i class="fa fa-user txt-color-teal"></i> Please provide a 3 to 10 char name for site display purposes. </b> </label> <!-- 长度错误提示 --> <div *ngIf="dnFormGroup.get('displayName').hasError('minlength')"> Display Name must be at least 3 characters </div> <div *ngIf="dnFormGroup.get('displayName').hasError('maxlength')"> Display Name cannot exceed 10 characters </div> <!-- 唯一性错误提示 --> <div *ngIf="dnFormGroup.get('displayName').hasError('displayNameTaken')"> Display Name is already used </div> </section> </div> </fieldset> </form>
额外优化建议
可以给异步验证器加上防抖,避免用户每输入一个字符就发起请求,提升用户体验:
- 把服务中的方法改成返回Observable:
import { Observable, of, catchError } from 'rxjs'; public validateDisplayName$(displayName: string): Observable<boolean> { console.log("AuthService validateDisplayName - displayName: " +displayName); return this.http.get<boolean>(`${environment.server_url}profile_cont/displaynameexists/${displayName}/`) .pipe( catchError(error => { console.log("AuthService validateDisplayName - error: " +error); return of(true); }) ); }
- 调整验证器使用Observable并添加防抖:
import { debounceTime, map, catchError } from 'rxjs/operators'; public validateDisplayName(control: AbstractControl): Observable<ValidationErrors | null> { return this.authService.validateDisplayName$(control.value) .pipe( debounceTime(300), // 300ms内输入不变化才发起请求 map(res => res ? { displayNameTaken: true } : null), catchError(() => of({ displayNameTaken: true })) ); }
内容的提问来源于stack exchange,提问作者BobC
相关产品推荐
相关产品推荐

