继Angular学习笔记50:响应式表单动态修改表单校验器之后,有时会遇到对于某一个控件,它要有整个系统的唯一性校验,但是又不想通过在用户填写完表单以后,再告知用户,填写的某一个控件内容重复,需要重新填写,这样的用户体验并不是最佳的,应在用户填写完具有唯一性校验的控件以后,就发起校验,然而发起校验是要通过远侧服务器来发起校验的,整个过程是异步完成的,所以需要一个异步校验器。
如何写一个异步的校验器呢?
言简意赅的讲,异步的校验器,其实也是一个服务,是实现了AsyncValidator接口的服务。
创建一个 async-demo 的组件
执行命令
ng g c async-demo
执行结果:
wujiayudeMBP:demo-test wjy$ ng g c async-demo
CREATE src/app/async-demo/async-demo.component.less (0 bytes)
CREATE src/app/async-demo/async-demo.component.html (29 bytes)
CREATE src/app/async-demo/async-demo.component.spec.ts (650 bytes)
CREATE src/app/async-demo/async-demo.component.ts (285 bytes)
UPDATE src/app/app.module.ts (2285 bytes)
在 async-demo 中添加一个表单和一个基本Input的控件
修改ansyc-demo的模版文件:
<nz-content>
<nz-divider nzText="异步校验器"></nz-divider>
<form nz-form [formGroup]="validateForm">
<nz-form-item>
<nz-form-label nzSpan="3" nz-col nzRequired>demo姓名</nz-form-label>
<nz-form-control nzSpan="6" nz-col>
<label>
<input nz-input formControlName="demoName" name="demoName" type="text">
</label>
</nz-form-control>
</nz-form-item>
</form>
</nz-content>
修改ansyc-demo的类文件:
import {Component, OnInit} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
@Component({
selector: 'app-async-demo',
templateUrl: './async-demo.component.html',
styleUrls: ['./async-demo.component.less']
})
export class AsyncDemoComponent implements OnInit {
public validateForm: FormGroup;
constructor(private fb: FormBuilder) {
this.validateForm = this.fb.group({
demoName: [null, [Validators.required]]
});
}
ngOnInit() {
}
}
创建一个服务,并实现AsyncValidator接口
- 创建服务
执行命令:
ng g s demoNameAsyncValidator
执行结果:
wujiayudeMBP:public wjy$ ng g s demoNameAsyncValidator
CREATE src/app/public/demo-name-async-validator.service.spec.ts (416 bytes)
CREATE src/app/public/demo-name-async-validator.service.ts (151 bytes)
- 实现AsyncValidator接口
import {Injectable} from '@angular/core';
import {AbstractControl, AsyncValidator, ValidationErrors} from '@angular/forms';
import {Observable, of} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class DemoNameAsyncValidatorService implements AsyncValidator {
constructor() {
}
validate(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
}
}
完善这个异步校验器
修改validate方法
import {Injectable} from '@angular/core';
import {AbstractControl, AsyncValidator, ValidationErrors} from '@angular/forms';
import {Observable, of} from 'rxjs';
import {HttpClient} from '@angular/common/http';
import {catchError, map} from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class DemoNameAsyncValidatorService implements AsyncValidator {
constructor(private httpService: HttpClient) {
}
validate(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
const validatorUrl = `/nest/demo?param=${control.value}`;
return control.dirty ? this.httpService.get(validatorUrl).pipe(map(isTrue => {
return (isTrue && control.dirty ? {demoNameValidator: true} : null);
}), catchError(() => null)) : of(null);
}
}
这里的接口是使用Nest.js写的,比较简单,接口中的逻辑是:只要接受到的参数是"demo" 就返回true,代表重复了,否则返回false,代表不重复。
Nest.js代码如下:
import { Controller, Get, Param, Query } from '@nestjs/common';
@Controller('demo')
export class DemoController {
@Get()
public handleValidatorName(@Query('param') param): boolean {
return param === 'demo';
}
}
将这个异步校验加到控件实例中:
修改组件async-demo的类文件
import {Component, OnInit} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {DemoNameAsyncValidatorService} from '../public/demo-name-async-validator.service';
@Component({
selector: 'app-async-demo',
templateUrl: './async-demo.component.html',
styleUrls: ['./async-demo.component.less']
})
export class AsyncDemoComponent implements OnInit {
public validateForm: FormGroup;
constructor(private fb: FormBuilder, private demoNameAsync: DemoNameAsyncValidatorService) {
this.validateForm = this.fb.group({
demoName: [null, [Validators.required], [this.demoNameAsync.validate.bind(this.demoNameAsync)]]
});
}
ngOnInit() {
}
}
保存运行;
然后就会发现,校验器会对每次输入的数据进行校验,这样的校验代价非常巨大,所以,在这里要修改检测方式。
减小校验代价
方式:修改检测方式由原有的 updateOn 属性从 change(默认值)改成 submit 或 blur;在这里修改为blur。
修改组件的类文件
import {Component, OnInit} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {DemoNameAsyncValidatorService} from '../public/demo-name-async-validator.service';
@Component({
selector: 'app-async-demo',
templateUrl: './async-demo.component.html',
styleUrls: ['./async-demo.component.less']
})
export class AsyncDemoComponent implements OnInit {
public validateForm: FormGroup;
constructor(private fb: FormBuilder, private demoNameAsync: DemoNameAsyncValidatorService) {
this.validateForm = this.fb.group({
demoName: [null, {
validators: [Validators.required],
asyncValidators: [this.demoNameAsync.validate.bind(this.demoNameAsync)],
updateOn: 'blur'
}]
});
}
ngOnInit() {
}
}
保存运行,会发现只有在失去焦点以后,才会发送异步校验的请求。
添加校验中的提示和校验失败的提示
在异步校验器发送请求的过程中,此控件的状态是pending的,所以,添加校验中的提示如下:
<nz-form-explain *ngIf="validateForm.get('demoName').dirty&& validateForm.get('demoName').pending">
正在校验中...
</nz-form-explain>
在异步校验器发送请求的完毕,根据远端服务器返回的结果判断,所以,添加校验失败提示如下:
<nz-form-explain *ngIf="validateForm.get('demoName').dirty
&& validateForm.get('demoName').hasError('demoNameValidator')">
填写内容重复,请重新填写...
</nz-form-explain>
所以修改后的组件类文件为:
<nz-content>
<nz-divider nzText="异步校验器"></nz-divider>
<form nz-form [formGroup]="validateForm">
<nz-form-item>
<nz-form-label nzSpan="3" nz-col nzRequired>demo姓名</nz-form-label>
<nz-form-control nzSpan="6" nz-col>
<label>
<input nz-input formControlName="demoName" name="demoName" type="text">
</label>
<nz-form-explain *ngIf="validateForm.get('demoName').dirty
&& validateForm.get('demoName').pending">
正在校验中...
</nz-form-explain>
<nz-form-explain *ngIf="validateForm.get('demoName').dirty
&& validateForm.get('demoName').hasError('demoNameValidator')">
填写内容重复,请重新填写...
</nz-form-explain>
</nz-form-control>
</nz-form-item>
</form>
</nz-content>
模版文件:
import {Component, OnInit} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {DemoNameAsyncValidatorService} from '../public/demo-name-async-validator.service';
@Component({
selector: 'app-async-demo',
templateUrl: './async-demo.component.html',
styleUrls: ['./async-demo.component.less']
})
export class AsyncDemoComponent implements OnInit {
public validateForm: FormGroup;
constructor(private fb: FormBuilder, private demoNameAsync: DemoNameAsyncValidatorService) {
this.validateForm = this.fb.group({
demoName: [null, {
validators: [Validators.required],
asyncValidators: [this.demoNameAsync.validate.bind(this.demoNameAsync)],
updateOn: 'blur'
}]
});
}
ngOnInit() {
}
}
异步校验器:
import {Injectable} from '@angular/core';
import {AbstractControl, AsyncValidator, ValidationErrors} from '@angular/forms';
import {Observable, of} from 'rxjs';
import {HttpClient} from '@angular/common/http';
import {catchError, map} from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class DemoNameAsyncValidatorService implements AsyncValidator {
constructor(private httpService: HttpClient) {
}
validate(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
const validatorUrl = `/Nest/demo?param=${control.value}`;
return control.dirty ? this.httpService.get(validatorUrl).pipe(map(isTrue => {
return (isTrue && control.dirty ? {demoNameValidator: true} : null);
}), catchError(() => null)) : of(null);
}
}
以上代码可以参考W先生的GitHub