Angular 表单验证

文章转自我的语雀:https://www.yuque.com/liuyin-zzwa0/angular/form-validation

模板驱动验证

模板驱动表单中添加验证机制,你要添加一些验证属性,就像原生的 HTML 表单验证器。 Angular 会用指令来匹配这些具有验证功能的指令。
每当表单控件中的值发生变化时,Angular 就会进行验证,并生成一个验证错误的列表(对应着 INVALID 状态)或者 null(对应着 VALID 状态)。

<input 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 alert-danger">
  <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 验证属性:requiredminlength。它还带有一个自定义的验证器指令 forbiddenName。要了解更多信息,参见自定义验证器一节。
  • name="ngModel" 把 NgModel 导出成了一个名叫 name 的局部变量。NgModel 把自己控制的 FormControl 实例的属性映射出去,让你能在模板中检查控件的状态,比如 validdirty。要了解完整的控件属性,参见 API 参考手册中的AbstractControl
  • <div> 元素的 *[ngIf](https://angular.cn/api/common/NgIf) 展示了一组嵌套的消息 div,但是只在有“name”错误和控制器为 dirty 或者 touched 时才出现。
  • 每个嵌套的 <div> 为其中一个可能出现的验证错误显示一条自定义消息。比如 requiredminlengthforbiddenName

以上及一下大多都是官方文档上Copy的,ε=ε=ε=┏(゜ロ゜;)┛

响应式表单的验证

在响应式表单中,权威数据源是其组件类。不应该通过模板上的属性来添加验证器,而应该在组件类中直接把验证器函数添加到表单控件模型上(FormControl)。然后,一旦控件发生了变化,Angular 就会调用这些函数。

验证器函数

有两种验证器函数:同步验证器和异步验证器。

  • 同步验证器函数接受一个控件实例,然后返回一组验证错误或 null。你可以在实例化一个 FormControl 时把它作为构造函数的第二个参数传进去。
  • 异步验证器函数接受一个控件实例,并返回一个承诺(Promise)或可观察对象(Observable),它们稍后会发出一组验证错误或者 null。你可以在实例化一个 FormControl 时把它作为构造函数的第三个参数传进去。

注意:出于性能方面的考虑,只有在所有同步验证器都通过之后,Angular 才会运行异步验证器。当每一个异步验证器都执行完之后,才会设置这些验证错误。

内置验证器

模板驱动表单中可用的那些属性型验证器

class Validators {
  static min(min: number): ValidatorFn
  static max(max: number): ValidatorFn
  static required(control: AbstractControl): ValidationErrors | null
  static requiredTrue(control: AbstractControl): ValidationErrors | null
  static email(control: AbstractControl): ValidationErrors | null
  static minLength(minLength: number): ValidatorFn
  static maxLength(maxLength: number): ValidatorFn
  static pattern(pattern: string | RegExp): ValidatorFn
  static nullValidator(control: AbstractControl): ValidationErrors | null
  static compose(validators: ValidatorFn[]): ValidatorFn | null
  static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn | null
}

用法如下:

this.validateForm = this.fb.group({
  userName: new FormControl(null, {
    validators: [Validators.required, Validators.pattern(/^[^\s]|[^\s]$/g)],
    updateOn: 'change'
  }),
  password: [null, [Validators.required, Validators.pattern(/^\S+$/)]],
  verifCode: [null, Validators.required]
});

自定义验证器

由于内置验证器无法适用于所有应用场景,有时候还是得创建自定义验证器。

export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
  return (control: AbstractControl): {[key: string]: any} | null => {
    const forbidden = nameRe.test(control.value);
    return forbidden ? {'forbiddenName': {value: control.value}} : null;
  };
}

forbiddenNameValidator 工厂函数返回配置好的验证器函数。 该函数接受一个 Angular 控制器对象,并在控制器值有效时返回 null,或无效时返回验证错误对象。

添加响应式表单

在响应式表单组件中,添加自定义验证器相当简单。你所要做的一切就是直接把这个函数传给 FormControl

this.heroForm = new FormGroup({
  'name': new FormControl('', [
    Validators.required,
    Validators.minLength(4),
    forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
  ]),
  'alterEgo': [null],
  'power': [null],
});

添加到模板驱动表单

在模板驱动表单中,你不用直接访问 FormControl 实例。所以不能像响应式表单中那样把验证器传进去,而应该在模板中添加一个指令。
ForbiddenValidatorDirective 指令相当于 forbiddenNameValidator 的包装器。
Angular 在验证过程中能识别出指令的作用,是因为指令把自己注册成了 NG_VALIDATORS 提供商,该提供商拥有一组可扩展的验证器。

@Directive({
  selector: '[appForbiddenName]',
  providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]
})
export class ForbiddenValidatorDirective implements Validator {
  @Input('appForbiddenName') forbiddenName: string;
 
  validate(control: AbstractControl): {[key: string]: any} | null {
    return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control)
                              : null;
  }
}
// use
providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]

使用

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

跨字段交叉验证

添加到响应式表单

实现要点AbstractControlOptions 接口类型:

export declare interface AbstractControlOptions {
  /**
     * @description
     * The list of validators applied to a control.
     */
  validators?: ValidatorFn | ValidatorFn[] | null;
  /**
     * @description
     * The list of async validators applied to control.
     */
  asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[] | null;
  /**
     * @description
     * The event name for control to update upon.
     */
  updateOn?: 'change' | 'blur' | 'submit';
}
this.heroForm = new FormGroup({
  'name': new FormControl(null, [
    Validators.required,
    Validators.minLength(4),
    forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
  ]),
  'alterEgo': [null]
  'power': [null, Validators.required]
}, {
    updateOn: 'change',
    validators: (form: FormGroup) => {
      const { controls: { name, alterEgo }, dirty } = form;
      if (dirty) {
       return alterEgo.value ? { 'identityRevealed': true } : null;
      }
      return null;
    }
});

添加到模板驱动表单中

创建一个指令,它会包装这个验证器函数。我们使用 NG_VALIDATORS 令牌来把它作为验证器提供出来。

@Directive({
  selector: '[appIdentityRevealed]',
  providers: [{ provide: NG_VALIDATORS, useExisting: IdentityRevealedValidatorDirective, multi: true }]
})
export class IdentityRevealedValidatorDirective implements Validator {
  validate(control: AbstractControl): ValidationErrors {
    return identityRevealedValidator(control)
  }
}

将指令添加到 HTML 模板中。由于验证器必须注册在表单的最高层,所以我们要把该指令放在 form 标签上。

<form #heroForm="ngForm" appIdentityRevealed>

显示校验信息

<div *ngIf="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)" class="cross-validation-error-message alert alert-danger">
    Name cannot match alter ego.
</div>

异步验证

响应式表单实现异步验证器

还是依靠 AbstractControlOptions 接口类型实现

this.heroForm = new FormGroup({
  'name': new FormControl(null, [
    Validators.required,
    Validators.minLength(4),
    forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
  ]),
  'alterEgo': [null]
  'power': [null, {
        updateOn: 'bulr',
    validators: Validators.required,
    asyncValidators: (form: FormGroup) => {
      const { controls: { name, alterEgo }, dirty } = form;
      return new Promise(resolve =>{
        if (dirty) {
         resolve(alterEgo.value ? { 'identityRevealed': true } : null);
        }
        resolve(null);
      });
    }
    }]
});

模板驱动表单实现自定义异步验证器

基础概念

就像同步验证器有 ValidatorFnValidator` 接口一样,异步验证器也有自己的对应物:AsyncValidatorFnAsyncValidator
它们非常像,但是有下列不同:

  • 它们必须返回承诺(Promise)或可观察对象(Observable),
  • 返回的可观察对象必须是有限的,也就是说,它必须在某个时间点结束(complete)。要把无尽的可观察对象转换成有限的,可以使用 firstlasttaketakeUntil 等过滤型管道对其进行处理。

注意!异步验证总是会在同步验证之后执行,并且只有当同步验证成功了之后才会执行。如果更基本的验证方法已经失败了,那么这能让表单避免进行可能会很昂贵的异步验证过程,比如 HTTP 请求。
在异步验证器开始之后,表单控件会进入 pending 状态。你可以监视该控件的 pending 属性,利用它来给用户一些视觉反馈,表明正在进行验证。

创建验证器类

@Injectable({ providedIn: 'root' })
export class UniqueAlterEgoValidator implements AsyncValidator {
  constructor(private heroesService: HeroesService) {}

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

在template中使用:

<input [(ngModel)]="name" #model="ngModel" UniqueAlterEgoValidator>
<app-spinner *ngIf="model.pending"></app-spinner>

异步验证性能上的注意事项

默认情况下,每当表单值变化之后,都会执行所有验证器。对于同步验证器,没有什么会显著影响应用性能的地方。不过,异步验证器通常会执行某种 HTTP 请求来对控件进行验证。如果在每次按键之后都发出 HTTP 请求会给后端 API 带来沉重的负担,应该尽量避免。
我们可以把 updateOn 属性从 change(默认值)改成 submitblur 来推迟表单验证的更新时机。

对于模板驱动表单:

<input [(ngModel)]="name" [ngModelOptions]="{updateOn: 'blur'}">

对于响应式表单:

new FormControl('', {updateOn: 'blur'});
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • 模板式表单验证(1) angular遇到form自动接管,不想自动接管,添加ngNoForm,当标签为div时,但...
    nzjcnjzx阅读 441评论 0 0
  • Angular 支持非常强大的内置表单验证,maxlength、minlength、required 以及 pat...
    sunny_lvy阅读 19,906评论 3 25
  • angular的表单验证由验证器Validators模块提供,所以如果你的表单需要验证就要将此模块引入 一、想使用...
    _花阅读 4,721评论 0 2
  • angular4里一个响应式编程的小例子 Angular2 响应式表单验证 Angular开发(十一)-关于响应式...
    飞鱼YE阅读 266评论 1 1
  • 一、实验目的 1、理解AES算法原理2、掌握AES的实现 二、实验环境 Windows XP、VC6.0/Ecli...
    蝌蚪1573阅读 1,497评论 0 0