Angular 表单(二)

Angular 为表单处理提供了丰富的支持,超越了常规的数据绑定,将表单处理作为顶级特性进行了专门的功能设计和开发。

校验器

Angular 的校验器分为两种:

  • 用户自定义的校验器
  • 预定义的校验器

预定义校验器

预定义的校验器,存在 Valicators 中,包含 reuiqred、 minlength、maxlength、pattern 等属性。

在响应式表单中,校验器作为模型类的构造函数传入模型类。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Validators } from '@angular/forms';

export class ReactivedFormComponent implements OnInit {
constructor() {
this.formModel = this.fb.group({
// 一个校验器
nickname: ['xxxx', Validators.required],
...
})
}

createUser(){
let nicknameValid:boolean = this.formModel.get('nickname2').valid;
console.log('nickname 是否校验通过:'+ nicknameValid);
}
}

如果想传入多个参数,可以传入一个数组。然后我们通过模型对象的 valid 属性来判断当前模型中的属性是否合法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Validators } from '@angular/forms';

export class ReactivedFormComponent implements OnInit {
constructor() {
this.formModel = this.fb.group({
// 如果想传入多个校验器,可传入一个数组
nickname: ['xxxx', [Validators.required, Validators.minLength(6)]],
...
})
}

createUser(){
let nicknameValid:boolean = this.formModel.get('nickname').valid;
console.log('nickname 是否校验通过:'+ nicknameValid);
}
}

当输入的字符数少于 6 个时,校验结果为 false。反之校验结果为 true。

此外,我们也可以通过 errors 属性来获取错误信息:

1
2
3
4
createUser() {
let nicknameErorrs: any = this.formModel.get('nickname').errors
console.log('nickname 是否校验通过:'+ JSON.stringify(nicknameErorrs));
}

errors 中注明了详细的错误原因:

自定义校验器

自定义的校验器是符合特定签名规则的方法,参数为 AbstractControl,返回值为一个对象,key 必须为字符串类型,值为任意类型。

1
2
3
xxx(param: AbstractControl): {[key: string]: any}{
return null;
}

注:AbstractControlFormArrayFormControlFormGroup 的父类,因此参数类型可以是三个中的任意一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export class ReactivedFormComponent implements OnInit {
...

mobileValidator(mobile: FormControl): any {
let value = (mobile.value || '') + '';
var myReg = /^(((13[0-9]{1})|(15[0-9]{1})|(18[0-9]{1}))+\d{8})$/;
let valid = myReg.test(value);
console.log('mobile 是否校验通过:'+ valid);
return valid ? null : {mobile: true};
}

constructor() {
this.formModel = this.fb.group({
...
mobile: ['',this.mobileValidator],
})
}
}

结果如下:

当我们需要同时校验多个字段,比如 passwordpasswordConfirm,方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export class ReactivedFormComponent implements OnInit {
...

passwordValidator(info: FormGroup): any {
let password: FormControl = info.get('password') as FormControl;
let passwordConfirm: FormControl = info.get('passwordConfirm') as FormControl;
let valid: boolean = password.value === passwordConfirm.value;
console.log('password 是否校验通过:'+ valid);
return valid ? null : {password: true};
}

constructor() {
this.formModel = this.fb.group({
...
passwordInfo: this.fb.group({
password: [''],
passwordConfirm: ['']
}, {validator: this.passwordValidator})
})
}
}

formModel 添加 validator 的方法不同,formGroup 添加 validator 时,需要添加一个对象,key 是 validator,value 指明了需要使用的校验器。

当我们确认密码输入与密码相同时,返回值变为 true。

info.get('password') 拿到的是一个父类抽象类,因此需要将它转换为具体的某个子类,添加 as FormControl

最后,我们可以通过 formModel 为整个表单添加验证,即当所有字段的值均合法,才能进行下一步操作。

1
2
3
4
5
createUser(){
if(this.formModel.valid) {
console.log(this.formModel.value);
}
}

当表单中某个字段不符合校验规则时,是无法打印出内容的。只有当表单中所有内容都通过校验,提交时才会执行相应的操作:

下面修改组件的模版,将错误信息显示给用户:

1
2
3
4
5
6
7
<form [formGroup]="formModel" (submit)="createUser()">
<div>用户名:<input formControlName="nickname" name="nickname" type="text" required patter="[a-zA-Z0-9]+"></div>
<div [hidden]="!formModel.hasError('required', 'nickname')">
用户名是必选项
</div>
...
</form>

hasError() 方法有两个参数,第一个参数是检查的校验错误信息 Error 的 key,只要 key 有值,则代表校验失败,已经返回了错误信息;第二个参数是要检查的字段名称。

注:嵌套属性,即 formGroup 中的某个属性,在绑定字段名称时,必须写成 formGroupName.formControlName,例如下面的 password 验证:

reactive-form.component.html

1
2
3
4
5
6
7
<div formGroupName="passwordInfo">
<div>密码:<input formControlName="password" name="password" type="password"></div>
<div [hidden]="!formModel.hasError('password', 'passwordInfo.password')">
密码是必填项
</div>
<div>确认密码:<input formControlName="passwordConfirm" name="passwordComfirm" type="password"></div>
</div>

上面我们将错误消息绑定在模版中,我们也可以将错误信息放在校验器中。即把下面的代码:

validators.ts

1
2
3
4
export function passwordValidator(info: FormGroup): any {
...
return valid ? null : {password: true};
}

reactived-form.component.html

1
2
3
<div [hidden]="!formModel.hasError('password', 'passwordInfo')">
密码不匹配
</div>

修改为:

validators.ts

1
2
3
4
export function passwordValidator(info: FormGroup): any {
...
return valid ? null : {password: {description: '密码和确认密码不匹配'}};
}

reactived-form.component.html

1
2
3
<div [hidden]="!formModel.hasError('password', 'passwordInfo')">
{{fomrModel.getError('password', 'passwordInfo')?.description}}
</div>

异步校验器

异步校验器允许调用远程的一个服务来进行验证,的返回是一个可观测的对象(流)。

下面我们实现了一个异步校验器,延迟 5 秒执行校验:

valicators.ts

1
2
3
4
5
6
import { Observable } from "rxjs";

export function mobileAsyncValidator(mobile: FormControl): any {
...
return Observable.of(valid ? null : {mobile: true}).delay(5000);
}

reactive-form.component.html

1
2
3
<div>
{{formModel.status}}
</div>

reactived-form.component.ts

1
2
3
4
5
6
7
8
export class ReactivedFormComponent implements OnInit {
constructor() {
this.formModel = this.fb.group({
...
mobile: ['', mobileValidator, mobileAsyncValidator],
})
}
}

至此,数组的三个参数已经全齐了。第一个是初始化的值,第二个同步校验器,第三个是异步校验器。

校验顺序是,当同步校验通过时,才会调用异步校验。在异步校验之后会先显示 PENDING 字样,5 秒后显示校验结果。