文章内容

2021/5/14 17:51:54,作 者: 黄兵

Angular 异步验证表单输入

在用户注册的时候,需要检测电子邮件是否已经存在,如果存在给出错误提示,这个时候就需要使用异步验证。

在Angular中异步验证器实现了 AsyncValidatorFnAsyncValidator 接口。它们与其同步版本非常相似,但有以下不同之处。

  • validate() 函数必须返回一个 Promise 或可观察对象,
  • 返回的可观察对象必须是有尽的,这意味着它必须在某个时刻完成(complete)。要把无尽的可观察对象转换成有尽的,可以在管道中加入过滤操作符,比如 first、last、take 或 takeUntil。

异步验证在同步验证完成后才会发生,并且只有在同步验证成功时才会执行。如果更基本的验证方法已经发现了无效输入,那么这种检查顺序就可以让表单避免使用昂贵的异步验证流程(例如 HTTP 请求)。

也就是先完成同步验证,之后在进行异步验证。

异步验证开始之后,表单控件就会进入 pending 状态。你可以检查控件的 pending 属性,并用它来给出对验证中的视觉反馈。

一种常见的 UI 模式是在执行异步验证时显示 Spinner(转轮)。下面的例子展示了如何在模板驱动表单中实现这一点。

在异步验证的时候,显示验证状态。

下面是根据官方的例子,自己实现的链接检查异步验证功能:

validator.service.ts

import {Injectable} from '@angular/core';
import {AbstractControl, AsyncValidator, ValidationErrors} from '@angular/forms';
import {catchError, map} from 'rxjs/operators';
import {Observable, of} from 'rxjs';
import {IPCrawlerService} from './ip-crawler/ip-crawler.service';

@Injectable({ providedIn: 'root' })
export class UniqueLinkTextValidator implements AsyncValidator {
constructor(private supportService: IPCrawlerService) {}

validate(ctrl: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
return this.supportService.verifySupportLinkText(ctrl.value).pipe(
map(isTaken => (isTaken ? { uniqueLinkText: true } : null)),
catchError(() => of(null))
);
}
}

验证器必须启动一个异步操作来查询链接是否已经在数据库。

supportService具体代码如下:

verifySupportLinkText(linkText: string): Observable<boolean> {
return this.http.get<boolean>(`${this.requestSupportUri}?uri=${linkText}`, {
headers: new HttpHeaders().set('Authorization', this.auth.accessToken)
});
}

当验证开始的时候,UniqueLinkTextValidator 把任务委托给 IPCrawlerService 的 verifySupportLinkText() 方法,并传入当前控件的值。这时候,该控件会被标记为 pending 状态,直到 validate() 方法所返回的可观察对象完成(complete)了。

verifySupportLinkText() 方法会调度一个 HTTP 请求来检查链接是否可用,并返回 Observable<boolean> 作为结果。validate() 方法通过 map 操作符来对响应对象进行管道化处理,并把它转换成验证结果。

与任何验证器一样,如果表单有效,该方法返回 null,如果无效,则返回 ValidationErrors。这个验证器使用 catchError 操作符来处理任何潜在的错误。在这个例子中,验证器将verifySupportLinkText() 错误视为成功的验证,因为未能发出验证请求并不一定意味着这个链接地址无效。你也可以用不同的方式处理这种错误,比如返回 ValidationError 对象。

一段时间过后,这条可观察对象链完成,异步验证也就完成了。pending 标志位也设置为 false,该表单的有效性也已更新。

后端具体写法如下:

if request.method == 'GET':
temp_list = []
get_link_text = request.args.get('uri', None)
if get_link_text:
# 检查链接文字是否存在
link_text = IPCrawlerSupport.query.filter_by(link_text=get_link_text).first()
if link_text:
# 链接文字存在
return jsonify(True)
else:
# 链接文字不存在
return jsonify(False)

这里直接根据情况返回boolean值。

优化异步验证器的性能

默认情况下,所有验证程序在每次表单值更改后都会运行。对于同步验证器,这通常不会对应用性能产生明显的影响。但是,异步验证器通常会执行某种 HTTP 请求来验证控件。每次按键后调度一次 HTTP 请求都会给后端 API 带来压力,应该尽可能避免。

你可以把 updateOn 属性从 change(默认值)改成 submit 或 blur 来推迟表单验证的更新时机。

linkTextFormControl: FormControl = new FormControl(null, {
updateOn: 'blur',
asyncValidators: [this.uniqueLinkTextValidator.validate.bind(this.uniqueLinkTextValidator)],
validators: [Validators.required,
Validators.maxLength(64)]
});

这里同时使用了同步验证器和异步验证器。

页面代码如下:

<mat-form-field appearance="fill" class="col-lg-6">
<mat-label>链接</mat-label>
<input matInput [formControl]="linkTextFormControl" placeholder="例如: web_site_online" maxlength="64" required>
<mat-hint align="end">{{linkTextFormControl.value?.length || 0}}/64</mat-hint>
<mat-hint align="start" *ngIf="linkTextFormControl.pending">正在验证链接是否可用...</mat-hint>
<mat-error *ngIf="linkTextFormControl.invalid">
<div *ngIf="linkTextFormControl.hasError('required')">
<mat-icon>info</mat-icon>请输入链接
</div>
<div *ngIf="linkTextFormControl.errors?.uniqueLinkText">
<mat-icon>info</mat-icon>链接不可用,请更换!
</div>
</mat-error>
</mat-form-field>

这里会根据不同错误显示错误提示。

可能上面理解有误,欢迎大家指正。


参考资料:

1、angular doc - 实现自定义异步验证器

2、Material Form field

3、Demo


黄兵个人博客原创。

转载请注明出处:黄兵个人博客 - Angular 异步验证表单输入

分享到:

发表评论

评论列表