angular表单验证

2023-11-05

表单验证

通常,我们都需要对用户的表单输入做验证,以保证数据的整体质量。

Angular也有两种验证表单的形式:

  • 使用属性验证,用于模板驱动表单;
  • 使用验证器函数进行验证,用于响应式表单。

验证器(Validator)函数

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

  • 同步验证器:接受控件实例,然后返回验证错误信息或 null。在实例化一个 FormControl时把它作为构造函数的第二个参数传进去;
  • 异步验证器 :接受实例并返回一个 PromiseObservable,稍后会发出一组验证错误或 null。也是在实例化 FormControl时,作为第三个参数传入。

内置验证器函数

Angular内置了一些基础功能的验证器,在日常开发中可以直接使用:

在这里插入图片描述

我们来简单使用一下内置的验证器:

// reactive-forms.component.ts
import {Validators} from '@angular/forms';
...
profileForm = this.fb.group({
  firstName: ['', Validators.required], // 必填
  lastName: ['',
    [Validators.required, Validators.minLength(4)]
  ], // 必填并且最小长度为4
  ...
});

// 因为我们可能多次获取表单中元素,所以先获取
get firstName() { return this.profileForm.get('firstName'); }
get lastName() { return this.profileForm.get('lastName'); }
<!-- reactive-forms.component.html -->
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
  <div class="form-group">
    <label>
      First Name: <input type="text" formControlName="firstName" class="form-control form-control-sm">
    </label>
    <div class="alert alert-danger">
      valid: {{firstName.valid | json}} <br>
      errors: {{firstName.errors | json}}
    </div>
  </div>
  <div class="form-group">
    <label>
      Last Name: <input type="text" formControlName="lastName" class="form-control form-control-sm">
    </label>
    <div class="alert alert-danger">
      valid: {{lastName.valid | json}} <br>
      errors: {{lastName.errors | json}}
    </div>
  </div>
  
</form>

效果是这样的:

在这里插入图片描述

可以看出:必填验证一开始都没有通过,验证顺序是跟添加验证器的顺序一致。

上一节最后我们不是介绍过 Angular跟踪控件状态吗,那就可以根据状态去控制错误提示。

在日常开发中,当我们表单是必填项,初始化页面时,我们是不应该提示错误,并且,如果用户聚焦后并没有输入任何值的时候,也是不应该提示错误的。

<!-- reactive-forms.component.html -->
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
  <div class="form-group">
    <label>
      First Name: <input type="text" formControlName="firstName" class="form-control form-control-sm">
    </label>
    <div *ngIf="firstName.dirty && firstName.errors" class="alert alert-danger">
      <span>请填写first name</span>
    </div>
  </div>
  <div class="form-group">
    <label>
      Last Name: <input type="text" formControlName="lastName" class="form-control form-control-sm">
    </label>
    <div *ngIf="lastName.dirty && lastName.errors as errors" class="alert alert-danger">
      <span *ngIf="errors.required">请填写last name</span>
      <span *ngIf="errors.minlength">last name应该至少4个字符</span>
    </div>
  </div>
</form>

在这里插入图片描述

上面的dirtyAbstractControl类的属性,用于判定控件是否修改过控件的值。

AbstractControl类还有其他监控用户操作控件的属性:

  • pristine: boolean如果用户尚未修改 UI中的值,则该控件是 pristine(原始状态)的。
  • touched: boolean一旦用户在控件上触发了 blur事件,则会将其标记为 touched
  • untouched: boolean如果用户尚未在控件上触发过 blur事件,则该控件为 untouched

定义自定义验证器

内置的验证器并不是总能适用于我们的需求,因此需要创建自定义验证器。

创建自定义验证器,我们需要遵从 Angular中的创建规则:

  • 验证器函数的返回值必须是 ValidatorFn类型;

    ValidatorFn实际上是一个接口,里面只有一个方法,将一个表单控件传入,返回验证错误信息( ValidationErrors)或者 null

在这里插入图片描述

  • ValidationErrors是一个 key: value的对象

在这里插入图片描述

搞清楚了规则,我们的验证器函数至少应该是这样的:

function validatorName(): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const isValid: boolean;
    return isValid ? null : {validateName: 'error info'};
  };
}

按照上面的样子,我们来写一个通过正则验证字符串中不能包含特定字符的验证器函数(新建validators.ts):

// validators.ts
import {AbstractControl, ValidationErrors, ValidatorFn} from '@angular/forms';

// 传入一个正则以及错误提示
export function forbiddenNameValidator(reg: RegExp, errorTips: string): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const forbidden = reg.test(control.value);
    return forbidden ? {forbiddenName: {value: errorTips}} : null;
  };
}

添加验证器:

// reactive-forms.component.ts
import {forbiddenNameValidator} from '../validators';
...
lastName: ['',
  [
    Validators.required,
    Validators.minLength(4),
    forbiddenNameValidator(/bob/i, '不能包含\"bob\"')
  ]
],
...

使用自定义验证器:

<!-- reactive-forms.component.html -->
<div class="form-group">
  <label>
    Last Name: <input type="text" formControlName="lastName" class="form-control form-control-sm">
  </label>
  <div *ngIf="lastName.dirty && lastName.errors as errors" class="alert alert-danger">
    <span *ngIf="errors.required">请填写last name</span>
    <span *ngIf="errors.minlength">last name应该至少4个字符</span>
    <span *ngIf="errors.forbiddenName">{{errors.forbiddenName.value}}</span>
  </div>
</div>

在这里插入图片描述

跨字段交叉验证

跨字段交叉验证其实就是对一组字段进行的验证,后面的值依赖于前面的值,实质也是一个自定义验证器。最常见的使用场景就是密码相同验证。

先将密码的基础架子搭起来:

// reactive-forms.component.ts
profileForm = this.fb.group({
  ...
  newPass: this.fb.group({
    password: ['', Validators.required],
    rePassword: ['', [Validators.required]]
  }),
});
get newPass() { return this.profileForm.get('newPass'); }
get password() { return this.profileForm.get('newPass').get('password'); }
get rePassword() { return this.profileForm.get('newPass').get('rePassword'); }
<!-- reactive-forms.component.html -->
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
  ...
  <div formGroupName="newPass">
    <div class="form-group">
      <label>
        输入密码: <input type="text" formControlName="password" class="form-control form-control-sm">
      </label>
      <div *ngIf="password.dirty && password.errors" class="alert alert-danger">
        <span>请填写密码</span>
      </div>
    </div>
    <div class="form-group">
      <label>
        再次输入密码: <input type="text" formControlName="rePassword" class="form-control form-control-sm">
      </label>
      {{rePassword}}
      <div *ngIf="rePassword.dirty && rePassword.errors" class="alert alert-danger">
        <span>请再次填写密码</span>
      </div>
    </div>
  </div>
</form>

自定义密码一致验证器函数:

// validators.ts
export function equalValidator(ctrl: AbstractControl): Validators | null {
  const password = ctrl.get('password');
  const rePassword = ctrl.get('rePassword');
  return password?.value === rePassword?.value ? null : {equal: '两次密码不一致'};
}

使用跨字段交叉验证:

FormBuilder下面的 group方法可以接收第二个参数,我们将验证器添加到里面:

在这里插入图片描述

// reactive-forms.component.ts
import {equalValidator} from '../validators';
newPass: this.fb.group({
  password: ['', Validators.required],
  rePassword: ['', [Validators.required]]
}, {validators: equalValidator}),

模板中错误提示:

<!-- reactive-forms.component.html -->
<div *ngIf="password.dirty && rePassword.dirty && newPass.errors" class="alert alert-danger">
  <span>{{newPass.errors.equal}}</span>
</div>

在这里插入图片描述

异步验证器

有的时候,用户填写的内容需要从服务器上的数据进行匹配,从而得出验证结果。这个时候,就需要使用异步验证器。

比如用户注册时候,需要验证用户手机号码是否已经使用。我们也将通过这个需求来介绍异步验证器。

先搭好静态的结构:

// reactive-forms.component.ts
profileForm = this.fb.group({
  mobile: ['',
    [
      Validators.required,
      Validators.pattern(/^\d{3}$/) // 这里的正则就简写成3位数字
    ]
  ],
});
get mobile() { return this.profileForm.get('mobile'); }
<!-- reactive-forms.component.html -->
<div *ngIf="password.dirty && rePassword.dirty && newPass.errors" class="alert alert-danger">
  <span>{{newPass.errors.equal}}</span>
</div>

通常情况下,我们获取服务器数据是在一个服务里面,那将异步验证器也放在服务里。

新建一个 hasMobile服务:

ng g s forms-study/hasMobile

在服务中实现 AsyncValidator接口:

// has-mobile.service.ts
export class HasMobileService implements AsyncValidator{
  // 我们这里直接模拟一下,实际项目中需要拿到服务器数据进行匹配
  validate(control: AbstractControl): Observable<ValidationErrors | null> {
    const random = Math.random();
    return iif(
      () => random > 0.5,
      of({ exists: '该手机已被注册'}),
      of(null)
    );
  }
}

使用异步验证器:

// reactive-forms.component.ts
constructor(private fb: FormBuilder, private hasMobileServer: HasMobileService) { }
profileForm = this.fb.group({
  mobile: ['',
    [
      Validators.required,
      Validators.pattern(/^\d{3}$/) // 这里的正则就简写成3位数字
    ],
    this.hasMobileServer.validate // 第三个参数接收一个异步验证器
  ],
});
get mobile() { return this.profileForm.get('mobile'); }
<!-- reactive-forms.component.html -->
<div class="form-group">
  <label>
    手机号: <input type="text" formControlName="mobile" class="form-control form-control-sm">
  </label>
  <div *ngIf="mobile.dirty && mobile.errors as errors" class="alert alert-danger">
    <span *ngIf="errors.required">请填写手机号</span>
    <span *ngIf="errors.pattern">手机号格式不正确</span>
    <span *ngIf="errors.exists">{{errors.exists}}</span>
  </div>
</div>

在这里插入图片描述

出于性能方面的考虑,只有在所有同步验证器都通过之后,Angular才会运行异步验证器。

Angular默认在输入框值发生变化时进行校验。如果我们的验证器中有异步验证器,每次变化都会向服务器发送请求,这是一个很消耗性能的操作。所以我们可以修改触发验证的时机为失去焦点时。

// reactive-forms.component.ts
mobile: ['', {
  validators: [
    Validators.required,
    Validators.pattern(/^\d{3}$/) // 这里的正则就简写成3位数字
  ],
  asyncValidators: this.hasMobileServer.validate,
  updateOn: 'blur'
}],

属性验证器

顾名思义,属性验证器是使用属性的形式来进行验证。同样也有内置验证器与自定义验证器。

我们将在上一节的模版驱动组件中演示属性验证器。

内置验证器

同样的,我们还是限定 firstName为必填项目,lastName为必填且最小长度为4的验证规则。

修改模版文件,给需要验证的控件添加必要的属性并拷贝前面的错误提示:

<!-- template-forms.component.html -->
<div class="form-group" >
  <label>
    <!-- 添加规则 required: 必填  -->
    First Name: <input type="text" required [(ngModel)]="model.firstName" name="firstName" class="form-control form-control-sm">
  </label>
  <!-- 拷贝的是下面的内容 -->
  <div *ngIf="firstName.dirty && firstName.errors" class="alert alert-danger">
    <span>请填写first name</span>
  </div>
</div>
<div class="form-group">
  <label>
    <!-- 添加规则 required: 必填 minlength="4" -->
    Last Name: <input type="text" required minlength="4" [(ngModel)]="model.lastName" name="lastName" required class="form-control form-control-sm">
  </label>
  <!-- 拷贝的是下面的内容 -->
  <div *ngIf="lastName.dirty && lastName.errors as errors" class="alert alert-danger">
    <span *ngIf="errors.required">请填写last name</span>
    <span *ngIf="errors.minlength">last name应该至少4个字符</span>
  </div>
</div>

这样,很显然是会报错,因为根本就没有 firstNamelastName对象。
想要跟响应式表单一样使用 FormControl对象,我们还需要在模版控件上把 ngModel导出成局部模板变量:

<!-- template-forms.component.html -->
...
<label>First Name:
  <input
    type="text"
    required
    [(ngModel)]="model.firstName"
    #firstName="ngModel"
    name="firstName"
    class="form-control form-control-sm">
</label>
...
<label>Last Name:
  <input 
    type="text" 
    required
    minlength="4"
    [(ngModel)]="model.lastName"
    #lastName="ngModel"
    name="lastName"
    class="form-control form-control-sm">
</label>
...

具体效果跟前面没任何差别,所以就不用图示范了。

自定义验证器

在模板驱动表单中,要使用自定义验证器,就要为模板添加一个指令,该指令包含了 validator函数。

新建 ForbiddenValidator指令:

ng g d forms-study/ForbiddenValidator

同样的,需要实现 Validator接口:

// forbidden-validator.directive.ts
import {Directive, Input} from '@angular/core';
import {AbstractControl, ValidationErrors, Validator} from '@angular/forms';
import {forbiddenNameValidator} from './validators';

@Directive({
  // 选择器修改成与输入属性一致
  selector: '[appForbiddenName]',
  // 把自己注册成了 NG_VALIDATORS 提供者,提供同步验证器。 multi表示一个令牌可以提供多个服务
  providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]
})
export class ForbiddenValidatorDirective implements Validator {
  // 定义输入属性,正则表达式  
  @Input('appForbiddenName') forbiddenName: string;
  // 错误提示
  @Input('appForbiddenNameTips') forbiddenNameTips: string;
  // 实现 validate 方法
  validate(control: AbstractControl): ValidationErrors | null {
    return this.forbiddenName ?
      forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'), this.forbiddenNameTips)(control) :
      null;
  }
}

使用自定义验证器:

<!-- template-forms.component.html -->
...
<div class="form-group">
  <label>Last Name:
    <input type="text"
           required
           minlength="4"
           [(ngModel)]="model.lastName"
           #lastName="ngModel"
           appForbiddenName="bob"
           appForbiddenNameTips="不能包含bob"
           name="lastName"
           class="form-control form-control-sm">
  </label>
  <div *ngIf="lastName.dirty && lastName.errors as errors" class="alert alert-danger">
    ...
    <span *ngIf="errors.forbiddenName">{{errors.forbiddenName.value}}</span>
  </div>
</div>

这样,我们就实现了跟上面一致效果的自定义验证器。

跨字段交叉验证

我们同样使用密码的例子进行示范。修改代码:

<!-- template-forms.component.html -->
<div class="form-group">
  <label>
    输入密码: <input type="text" required [(ngModel)]="model.password" #password="ngModel" name="password" class="form-control form-control-sm">
  </label>
  <div *ngIf="password.dirty && password.errors" class="alert alert-danger">
    <span>请填写密码</span>
  </div>
</div> 
<div class="form-group">
  <label>
    再次输入密码: <input type="text" required [(ngModel)]="model.rePassword" #rePassword="ngModel" name="rePassword" class="form-control form-control-sm">
  </label>
  <div *ngIf="rePassword.dirty && rePassword.errors" class="alert alert-danger">
    <span>请填写密码</span>
  </div>
</div>    

前面已经说过,交叉验证也是一种自定义验证器,所以,我们还是需要新建一个指令:

ng g d forms-study/PsdEqualValidator

同样需要实现 Validator接口:

// psd-equal-validator.directive.ts
import { Directive } from '@angular/core';
import {AbstractControl, NG_VALIDATORS, ValidationErrors, Validator} from '@angular/forms';
import {equalValidator} from './validators';

@Directive({
  selector: '[appPsdEqualValidator]',
  providers: [{provide: NG_VALIDATORS, useExisting: PsdEqualValidatorDirective, multi: true}]
})
export class PsdEqualValidatorDirective implements Validator{
  validate(control: AbstractControl): ValidationErrors | null {
    return equalValidator(control);
  }
}

因为模版驱动这里没有表单嵌套一说,所以我们需要在顶级表单中使用交叉验证器:

<!-- template-forms.component.html -->
<form #profileForm="ngForm" appPsdEqualValidator (ngSubmit)="onSubmit(profileForm)">
  ...
  <div *ngIf="password.dirty && rePassword.dirty && profileForm.errors" class="alert alert-danger">
    <span>{{profileForm.errors.equal}}</span>
  </div>
</form>

异步验证器

同理,创建一个 hasMobile指令并实现 AsyncValidator接口:

// has-mobile-validator.directive.ts
import { Directive } from '@angular/core';
import {AbstractControl, AsyncValidator, NG_ASYNC_VALIDATORS, ValidationErrors} from '@angular/forms';
import {Observable} from 'rxjs';
import {HasMobileService} from './has-mobile.service';

@Directive({
  selector: '[appHasMobileValidator]',
  // 注意:这里是 NG_ASYNC_VALIDATORS
  providers: [{provide: NG_ASYNC_VALIDATORS, useExisting: HasMobileValidatorDirective, multi: true}]
})
export class HasMobileValidatorDirective implements AsyncValidator{
  constructor(private hasMobileServer: HasMobileService) { }
  validate(control: AbstractControl): Observable<ValidationErrors | null> {
    return this.hasMobileServer.validate(control);
  }
}

使用验证器,并设置验证时机为失去焦点时触发:

<!-- template-forms.component.html -->
<div class="form-group">
  <label>
    手机号: <input
    type="text"
    required
    pattern="\d{3}"
    [(ngModel)]="model.mobile"
    [ngModelOptions]="{updateOn: 'blur'}"
    #mobile="ngModel"
    appHasMobileValidator
    name="mobile"
    class="form-control form-control-sm">
  </label>
  <div *ngIf="mobile.dirty && mobile.errors as errors" class="alert alert-danger">
    <span *ngIf="errors.required">请填写手机号</span>
    <span *ngIf="errors.pattern">手机号格式不正确</span>
    <span *ngIf="errors.exists">{{errors.exists}}</span>
  </div>
</div>

总结

这一节我们介绍了表单验证大部分知识,其他未提到的在使用时查找官方文档即可。

  1. 本节主要介绍了响应式表单的验证及模板驱动表单的验证;
  2. 他们都可以使用内置验证器、自定义验证器、交叉验证器、异步验证器;
  3. 创建自定义验证器需要遵从 Angular相关规则,实现对应接口以及返回 ValidationErrors或者 null;
  4. 响应式表单都异步验证器通过服务实现,模版驱动表单的自定义验证器是通过指令实现。

欢迎关注我的公众号,公众号将第一时间更新angular教程:
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

angular表单验证 的相关文章

随机推荐

  • 最新CTR预测服务的GPU优化实践

    CTR模型在互联网的搜索 推荐 广告等场景有着广泛的应用 近年来 随着深度神经网络的引入 CTR模型的推理对硬件算力的要求逐渐增加 本文介绍了美团在CTR模型优化的实践 通过分析模型结构特点 结合GPU硬件架构 我们设计了一系列流程对模型进
  • 目标检测标签文件txt转成xml

    最近在用ppyolo训练好的模型对新采集的数据进行标记 再人工微调 减少从头打标签的时间 但是推理保存的结果都是txt格式的 想要在labelimg中可视化 那就需要将txt转换成xml 以下代码即可完成这一功能 coding UTF 8
  • Web_Components 系列(九)—— Shadow Host 的 CSS 选择器

    前言 在上一节我们了解了如何给自定义组件设置样式 当时是将自定义标签的样式设置在主 DOM 中的
  • docker从安装到入门(centos7连不上网)

    我们安装好centos7之后 只用网络链接的NAT模式 主机有网你就有网 傻瓜式链接 但是发现ping不通百度 通过ifconfig a发现ens33没有ip 虽然和这个没关系 因为用的是NAT模式 我们编辑vi etc sysconfig
  • OpenGL error 0x0502

    项目出现OpenGL error 0x0502 导致有些UI绘制不出来 大致情况是这样 游戏切换到后台之后 其他玩家发来语音 在语音还在播放的时候 切换回前台 这个时候语音服务器 用的融云 会推送到安卓底层 安卓底层有回调到cocos2dx
  • Linux系统之neofetch工具的基本使用

    Linux系统之neofetch工具的基本使用 一 neofetch工具介绍 1 1 neofetch简介 1 2 neofetch特点 二 检查本地环境 2 1 检查操作系统版本 2 2 检查内核版本 三 安装neofetch工具 3 1
  • VMware Workstation Pro 安装教程

    文章目录 笔者的运行环境 VMware Workstation 16 Pro Red Hat Enterprise Linux 8 3 0 需要提前一个操作系统的镜像文件 ISO 这个文件与 VMware 无关 实际上 在安装完 VMwar
  • 生成字典的三种方式

    字典是记录一些特殊或有目的性的密码集合 通常以txt格式进行记录保存 在渗透许多服务器 smb ftp ssh 远程桌面rdp 网页后台等一些用户登录时 没有正确密码 使用密码字典爆破就是最直接的黑客攻击方法 一 使用cupp工具生成 1
  • 对输入数据排序后进行二分查找(C语言)

    输入数据后的排序方法有很多种 这里我用的是暴力排序 各位友友们可以尝试更改排序方法 include
  • Python selenium 滚动页面以及滚动至元素可见之详细讲解

    我们滚动浏览器页面向上 下 左右可以用一下代码 向上和向左需要加 向下滚动xx个像素 driver execute script window scrollBy 0 xx 向上滚动x个像素 driver execute script win
  • JM解码(一):参考帧列表和DPB处理

    以P帧为例 void alloc ref pic list reordering buffer Slice currSlice int size currSlice gt num ref idx active LIST 0 1 if cur
  • 谷粒商城-分布式高级篇[商城业务-订单服务]

    谷粒商城 分布式基础篇 环境准备 谷粒商城 分布式基础 业务编写 谷粒商城 分布式高级篇 业务编写 持续更新 谷粒商城 分布式高级篇 ElasticSearch 谷粒商城 分布式高级篇 分布式锁与缓存 项目托管于gitee 一 页面环境搭建
  • ubuntu freeradius 3.0 + mariadb

    安装数据库及Radius sudo apt update sudo apt install y freeradius freeradius mysql freeradius utils mariadb server mariadb clie
  • Unity Rotate鼠标控制人物旋转

    添加碰撞盒 一定要添加碰撞盒才能响应鼠标事件 将碰撞盒复制给骨架 如果鼠标划动的向量 X轴大于Y轴 则是左右划动 让它旋转 SpinWithMouse using System Collections using System Collec
  • 窗体,组件,事件

    窗体对象JFrame package frame import javax swing public class JFrameTest public static void main String args 创建窗体对象 JFrame jF
  • 使用javacv中的ffmpeg实现录屏,结果连运行都失败了,现在终于解决了

    前言 今天突发奇想 想自己写一个录屏的软件 上次写了一个专门录音的Demo 但是要把声音和视频放到一起合成一个mp4文件 着实有一点艰难 所以就打算使用ffmpeg来写一个 而这篇博客中会顺便谈一谈我碰到的各种坑 ffmpeg是一个c 程序
  • 中兴EPON OLT-C300开局配置

    一 基础配置 1 自定义时间 clock set hh mm ss Apr 8 2018 con t username zte password zte privilege 15 用户名密码 2 自定义名称 hostname CeShi O
  • js如何进行数组去重?

    1 数组反转 使用 reverse 实现数组反转 const arr 1 2 3 console log arr 1 2 3 arr reverse console log arr 3 2 1 2 数组去重 1 new Set array
  • python学习语法中与c语言不同之处(1)

    一 发现使用打印使用的是print 而在C语言中我们更多的使用的是printf 比如想要打印出来hello world 直接如下 C语言 printf a d a python语言 print hello world 然后就是直接回车键就可
  • angular表单验证

    表单验证 通常 我们都需要对用户的表单输入做验证 以保证数据的整体质量 Angular也有两种验证表单的形式 使用属性验证 用于模板驱动表单 使用验证器函数进行验证 用于响应式表单 验证器 Validator 函数 验证器函数可以是同步函数