3

引言

表单需要收集一些有效的数据,然而用户是可以输入各种各样的数据,使用验证器就可以约束用户填写数据的格式。前端验证可以提升用户体验(反馈快),减少服务器压力(减少无意义的请求)

有必要在前端校验的:必填项、(邮箱、电话号、地址)格式、密码强度检测
必须后端校验的:唯一性验证、验证码、敏感词。出错概率高的要做异步校验

模板驱动验证

模板驱动表单中添加验证机制,你要添加一些验证属性,就像原生的 HTML 表单验证器一样。 Angular 会用指令来匹配这些具有验证功能的指令。
例子:

<input type="text" id="name" name="name" class="form-control"
      required minlength="4" appForbiddenName="bob"
      [(ngModel)]="hero.name" #name="ngModel">

<div *ngIf="name.invalid && (name.dirty || name.touched)"
    class="alert">

  <div *ngIf="name.errors?.['required']">
    Name is required.
  </div>
  <div *ngIf="name.errors?.['minlength']">
    Name must be at least 4 characters long.
  </div>
  <div *ngIf="name.errors?.['forbiddenName']">
    Name cannot be Bob.
  </div>

</div>

其中<input> 元素带有一些 HTML 验证属性:required 和 minlength内置验证器。它还带有一个自定义的验证器指令 forbiddenName。

@Directive({
  selector: '[appForbiddenName]',
  providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]
})
export class ForbiddenValidatorDirective implements Validator {
  @Input('appForbiddenName') forbiddenName = '';

  validate(control: AbstractControl): ValidationErrors | null {
    if(control.value !== this.forbiddenName){
      return null
    }
    return this.forbiddenName ? {forbiddenName: true}: null;
  }
}

效果图如下:

响应式表单中验证输入

验证器(Validator)函数可以是同步函数,也可以是异步函数。

创建表单并添加验证器:

  formGroup  = new FormGroup({
    name: new FormControl<string>('', Validators.required),
    username: new FormControl<string>('', [Validators.required, this.commonValidator.username], [this.commonValidator.userUniqueUsername()]),
    contactPhone: new FormControl<string>('', [Validators.required, this.commonValidator.phone], this.commonValidator.userUniqueContactPhone()),
    role: new FormControl<Role>(null, Validators.required),
  });

FormControl控件可传三个参数,第一个参数是控件的value,必填,第二个参数validatorOrOpts?,同步验证器选填, 第三个参数asyncValidator?,异步验证器选填。

    new <T = any>(value: FormControlState<T> | T, validatorOrOpts?: ValidatorFn | ValidatorFn[] | FormControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormControl<T | null>;

如下是验证手机号是否相同的自定义同步验证器

  /**
   * 同步验证
   * 验证 电话号码 的格式
   * @param control
   * @return 通过验证返回null否则返回ValidationErrors
   */
  phone(control: AbstractControl): ValidationErrors | null {
    const phone = control.value;
    if (/^1\d{10}$/.test(phone)) {
      return null;
    }
    return phone === '' ? {required: '电话号码为空'} : {phone: '电话号码格式不符合'};
  }

如下是验证手机号是否相同的自定义异步验证器

  /**
   * 电话号码异步验证器 用户手机号是否已经存在
   */
  userUniqueContactPhone(initValue?: string): (control: AbstractControl) => Observable<ValidationErrors | null> {
    return (control: AbstractControl): Observable<ValidationErrors | null> => {
      const contactPhone = control.value;
      if (contactPhone === '' || contactPhone?.trim() === initValue) {
        return of(null);
      }
      return CommonValidator.userService.contactPhoneExist(contactPhone)
        .pipe(map(data => {
          return data ? {uniqueContactPhone: data} : null;
        }));
    };
  }

优化异步验证器的性能

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

解决方法:
1在控件中修改updateOn属性, 你可以把 updateOn 属性从 change(默认值)改成 submit(提交) 或 blur(失去焦点) 来推迟表单验证的更新时机。

updateOn?: 'change' | 'blur' | 'submit';

运用:

  formGroup  = new FormGroup({
    name: new FormControl<string>(''),
    username: new FormControl<string>('', [Validators.required, this.commonValidator.username], [this.commonValidator.userUniqueUsername()]),
    contactPhone: new FormControl<string>('',  {
      validators: [Validators.required, this.commonValidator.username],
      asyncValidators: [this.commonValidator.userUniqueUsername()],
      updateOn: 'blur'
    }),
    role: new FormControl<Role>(null, Validators.required),
  });

2给异步验证器添加防抖,学长的文章有详细讲解https://segmentfault.com/a/1190000041621553

跨字段交叉验证

除了对单个控件进行验证外,angular也支持跨字段进行交叉验证,跨字段验证是指基于多个表单字段的值进行验证的验证规则
以下是一个示例,展示了如何实现一个简单的密码确认验证器来验证两个密码字段是否匹配:

// 创建一个自定义验证器函数
export function passwordMatchValidator(control: AbstractControl): ValidationErrors | null {
  const password = control.get('password');
  const confirmPassword = control.get('confirmPassword');

  if (password.value !== confirmPassword.value) {
    return { passwordMismatch: true };
  }

  return null;
}

// 在表单组中使用这个验证器函数
myForm = new FormGroup({
  password: new FormControl('', Validators.required),
  confirmPassword: new FormControl('', Validators.required)
}, { validators: passwordMatchValidator });

passwordMatchValidator 函数会接收整个表单组作为参数。然后,通过 get 方法获取 password 和 confirmPassword 字段的值,并对它们进行比较。

老师有文章讲关于Angular如何在跨字段验证器中直接调用其它独立的验证器

总结

第一次尝试写文章, 存在很多不足,也有点茫然不知道该怎么写,希望跟着团队慢慢成长。


吴季分
390 声望13 粉丝