详解Angular Reactive Form 表单验证


Posted in Javascript onJuly 06, 2017

本文我们将介绍 Reactive Form 表单验证的相关知识,具体内容如下:

  1. 使用内建的验证规则
  2. 动态调整验证规则
  3. 自定义验证器
  4. 自定义验证器 (支持参数)
  5. 跨字段验证

基础知识

内建验证规则

Angular 提供了一些内建的 validators,我们可以在 Template-Driven 或 Reactive 表单中使用它们。

目前 Angular 支持的内建 validators 如下:

  1. required - 设置表单控件值是非空的。
  2. email - 设置表单控件值的格式是 email。
  3. minlength - 设置表单控件值的最小长度。
  4. maxlength - 设置表单控件值的最大长度。
  5. pattern - 设置表单控件的值需匹配 pattern 对应的模式。

示例

this.signupForm = this.fb.group({
 userName: ['', [Validators.required, Validators.minLength(3)]],
 email: ['', [Validators.required, Validators.pattern('[a-z0-9._%+_]+@[a-z0-9.-]+')]]
});

动态调整验证规则

myControl.setValidators(Validators.required);
myControl.setValidators([Validators.required, Validators.maxLength(6)]);

myControl.clearValidators();
myControl.updateValueAndValidity();

自定义验证器

function myCustomValidator(c: AbstractControl): 
 {[key: string]: boolean} | null {
 if(somethingIsWrong) {
  return { 'myvalidator': true};
 }
 return null;
}

自定义验证器 (支持参数)

function myCustomValidator(param: any): ValidatorFn {
 return (c: AbstractControl): {[key: string]: boolean} | null {
  if(somethingIsWrong) {
   return { 'myvalidator': true};
  }
   return null;
 }
}

跨字段验证

emailMatcher

function emailMatcher(c: AbstractControl) {
 let emailControl = c.get('email');
 let confirmControl = c.get('confirmEmail');

 if (emailControl.pristine || confirmControl.pristine) {
  return null;
 }

 return emailControl.value === confirmControl.value ? null : { 'match': true };
}

emailGroup

ngOnInit(): void {
 this.signupForm = this.fb.group({
  userName: ['', [Validators.required, Validators.minLength(6)]],
  emailGroup: this.fb.group({
   email: ['', [Validators.required, Validators.email]],
   confirmEmail: ['', [Validators.required]],
  }, { validator: emailMatcher })
});

在介绍表单验证前,我们来看一下目前页面的显示效果:

详解Angular Reactive Form 表单验证

表单验证

表单的内建验证规则,前面章节已经介绍过了,接下来我们来介绍在表单中如何 "动态调整验证规则" 。

动态调整验证规则

为了演示 "动态调整验证规则" 的功能,我新增了两个控件:

  1. radio - 用于让用户设置是否开启手机登录。
  2. tel - 当用户开启手机登录功能,用于让用户输入手机号码。

当用户开启手机登录功能,手机号码对应控件的验证规则,必须是必填且格式为合法的手机号码。当用户不开启手机登录功能时,手机号码对应控件将不是必填的。

新增 radio 控件

<div class="form-group">
  <div class="col-md-offset-1 col-md-8 checkbox">
   开启手机登录
   <label>
     <input type="radio" value="1"
      formControlName="enableMobile">
       是
   </label>
   <label>
     <input type="radio" value="0"
      formControlName="enableMobile">
       否
   </label>
  </div>
</div>

新增 tel 控件

<div class="form-group"
  [ngClass]="{'has-error': (mobile.touched || mobile.dirty) && !mobile.valid }">
     <label class="col-md-2 control-label"
         for="mobileId">手机号码</label>

     <div class="col-md-8">
      <input class="form-control"
          id="mobileId"
          type="text"
          placeholder="请输入手机号码"
          formControlName="mobile"/>
      <span class="help-block" *ngIf="(mobile.touched || mobile.dirty) 
        && mobile.errors">
         <span *ngIf="mobile.errors.required">
           请输入手机号码
         </span>
         <span *ngIf="mobile.errors.minlength">
           手机号码格式不正确
         </span>
      </span>
     </div>
</div>

动态调整验证规则功能

ngOnInit(): void {
  ...
  this.signupForm.get('enableMobile').valueChanges
   .subscribe(value => this.checkMobile(value));
}

checkMobile(enableMobile: string): void {
 const mobileControl = this.signupForm.get('mobile');
 
 enableMobile === "1" ? 
   mobileControl.setValidators([Validators.required,
    Validators.pattern('1(3|4|5|7|8)\\d{9}')]) :
   mobileControl.clearValidators();
 
  mobileControl.updateValueAndValidity();
}

介绍完如何动态调整验证规则,接下来我们来介绍如何 "自定义验证器"。

自定义验证器

为了演示 "自定义验证器" 的功能,我新增了一个控件:

number - 用于让用户设置是年龄信息。

当让用户手动输入年龄信息时,我们需要设置一个有效的年龄范围,比如 (18 - 120)。此时我们就需要通过自定义验证器来实现上述功能。

新增 number 控件

<div class="form-group"
  [ngClass]="{'has-error': (age.touched || age.dirty) && !age.valid }">
  <label class="col-md-2 control-label"
   for="ageId">年龄</label>

   <div class="col-md-8">
     <input class="form-control"
         id="ageId"
         type="number"
         placeholder="请输入年龄"
         formControlName="age"/>
     <span class="help-block" *ngIf="(age.touched || age.dirty) && age.errors">
         <span *ngIf="age.errors.range">
           输入年龄不合法
         </span>
     </span>
   </div>
</div>

自定义验证器模板

function myCustomValidator(c: AbstractControl): 
 {[key: string]: boolean} | null {
 if(somethingIsWrong) {
  return { 'myvalidator': true};
 }
 return null;
}

新增 ageValidator 验证器

function ageValidator(c: AbstractControl): { [key: string]: any } | null {
 let age = c.value;
 if (age && (isNaN(age) || age < 20 || age > 120)) {
  return { 'range': true, min: 20, max: 120 };
 }
 return null;
}

使用 ageValidator 验证器

ngOnInit(): void {
 this.signupForm = this.fb.group({
  // ...
  age: ['', ageValidator]
 });
}

我们的 ageValidator 自定义验证器,虽然已经实现了。细心的读者应该会发现,在 ageValidator 验证器内部,我们写死了年龄的边界值 (最小值与最大值)。理想的情况下,应该能够让用户自行设定边界值。因此接下来,我们来优化一下 ageValidator 验证器。

自定义验证器 (支持参数)

自定义验证器模板 (支持参数)

function myCustomValidator(param: any): ValidatorFn {
 return (c: AbstractControl): {[key: string]: boolean} | null {
  if(somethingIsWrong) {
   return { 'myvalidator': true};
  }
   return null;
 }
}

新增 ageRange 验证器工厂

function ageRange(min: number, max: number): ValidatorFn {
 return (c: AbstractControl): { [key: string]: any } | null => {
  let age = c.value;
  if (age && (isNaN(age) || age < min || age > max)) {
   return { 'range': true, min: min, max: max };
  }
  return null;
 }
}

使用 ageRange 验证器工厂

ngOnInit(): void {
 this.signupForm = this.fb.group({
  // ...
  age: ['', ageRange(20, 120)]
 });
}

介绍完如何自定义验证器,接下来我们来介绍如何实现 "跨字段验证" 的功能。

跨字段验证

在日常生活中,在注册表单中,经常要让用户再次输入同样的字段值,比如登录密码或邮箱地址的值。针对这种场景,我们就需要验证两个控件的输入值是否一致,这时我们就要引入跨字段验证的功能。

为了演示 "跨字段验证" 的功能,我新增了一个控件:

  1. email - 用于让用户确认输入的邮箱地址

新增 email 控件

<label class="col-md-2 control-label"
    for="emailId">确认邮箱</label>
<div class="col-md-8">
   <input class="form-control"
      id="confirmEmailId"
      type="email"
      placeholder="请再次输入邮箱地址"
      formControlName="confirmEmail"/>
   <span class="help-block" *ngIf="(confirmEmail.touched || 
      confirmEmail.dirty)">
      <span *ngIf="confirmEmail.errors?.required">
        请输入邮箱地址
      </span>
      <span *ngIf="!confirmEmail.errors?.required && 
        emailGroup.errors?.match">
        两次输入的邮箱地址不一致
      </span>
   </span>
</div>

新增 emailMatcher

function emailMatcher(c: AbstractControl) {
 let emailControl = c.get('email');
 let confirmControl = c.get('confirmEmail');
 if (emailControl.pristine || confirmControl.pristine) {
  return null;
 }
 return emailControl.value === confirmControl.value ? null : { 'match': true };
}

新增 emailGroup

ngOnInit(): void {
 this.signupForm = this.fb.group({
  userName: ['', [Validators.required, Validators.minLength(6)]],
  emailGroup: this.fb.group({
   email: ['', [Validators.required, Validators.email]],
   confirmEmail: ['', [Validators.required]],
  }, { validator: emailMatcher }),
  // ...
});

更新模板

<div class="form-group"
   formGroupName="emailGroup"
   [ngClass]="{'has-error': emailGroup.errors }">
     <label class="col-md-2 control-label"
         for="emailId">邮箱</label>
     <div class="col-md-8">
      <input class="form-control"
          id="emailId"
          type="email"
          placeholder="请输入邮箱地址"
          formControlName="email"/>
      <span class="help-block" *ngIf="(email.touched || email.dirty) &&
         email.errors">
         <span *ngIf="email.errors.required">
           请输入邮箱地址
         </span>
         <span *ngIf="!email.errors.required && email.errors.email">
           请输入有效的邮箱地址
         </span>
      </span>
     </div>
     <!--其余部分:请参考"新增email控件"的内容-->
</div>

上面代码中,有以下几个问题需要注意:

  1. Form Group 是可以嵌套使用的。
this.signupForm = this.fb.group({
  userName: ['', [Validators.required, Validators.minLength(6)]],
  emailGroup: this.fb.group({
   email: ['', [Validators.required, Validators.email]],
   confirmEmail: ['', [Validators.required]],
}, { validator: emailMatcher })

我们通过 formGroupName="groupName" 语法来绑定内嵌的 Form Group。

<div class="form-group" formGroupName="emailGroup"
  [ngClass]="{'has-error': emailGroup.errors }">

邮箱不匹配的信息是存在 emailGroup 对象的 errors 属性中,而不是存在 confirmEmail 对象的 errors 属性中。

<span *ngIf="!confirmEmail.errors?.required && emailGroup.errors?.match">
  两次输入的邮箱地址不一致
</span>

我有话说

怎么会监听表单值的变化?

Reactive Form

export class AppComponent {
 constructor(private fb: FormBuilder) {
  this.form = fb.group({
   name: 'semlinker',
   age: 31
  });

  this.form.valueChanges.subscribe(data => {
   console.log('Form changes', data)
  });
 }
}

Template-driven Form

模板

<form #myForm="ngForm" (ngSubmit)="onSubmit()">
 <input type="text"
  name="name" 
  class="form-control" 
  required   
  [(ngModel)]="user.name">
 <input type="number" 
   name="age" 
   class="form-control" 
   required 
  [(ngModel)]="user.age">
</form>

组件类

class AppComponent implements AfterViewInit {
 @ViewChild('myForm') form;

 ngAfterViewInit() {
  this.form.control.valueChanges
   .debounceTime(500)
   .subscribe(values => this.doSomething(values));
 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript Distilled 基础知识与函数
Apr 07 Javascript
jquery.pagination.js 无刷新分页实现步骤分享
May 23 Javascript
jQuery.event兼容各浏览器的event详细解析
Dec 18 Javascript
jquery的ajax简单结构示例代码
Feb 17 Javascript
jQuery实现预加载图片的方法
Mar 17 Javascript
全面解析Bootstrap中tab(选项卡)的使用方法
Jun 06 Javascript
微信小程序 wxapp视图容器 view详解
Oct 31 Javascript
基于JavaScript实现Tab选项卡切换效果
Nov 24 Javascript
jquery的 filter()方法使用教程
Mar 22 jQuery
vue实现2048小游戏功能思路详解
May 09 Javascript
Vue实现简易计算器
Feb 25 Javascript
详解webpack-dev-middleware 源码解读
Mar 23 Javascript
让div运动起来 js实现缓动效果
Jul 06 #Javascript
老生常谈Bootstrap媒体对象
Jul 06 #Javascript
jQuery实现选中行变色效果(实例讲解)
Jul 06 #jQuery
运用jQuery写的验证表单(实例讲解)
Jul 06 #jQuery
Textarea输入字数限制实例(兼容iOS&amp;安卓)
Jul 06 #Javascript
react系列从零开始_简单谈谈react
Jul 06 #Javascript
鼠标拖动改变DIV等网页元素的大小的实现方法
Jul 06 #Javascript
You might like
目录,文件操作详谈―PHP
2006/11/25 PHP
使用PHPMYADMIN操作mysql数据库添加新用户和数据库的方法
2010/04/02 PHP
浅析PHP安装扩展mcrypt以及相关依赖项(PHP安装PECL扩展的方法)
2013/07/05 PHP
php中删除数组的第一个元素和最后一个元素的函数
2015/03/07 PHP
Symfony2框架学习笔记之HTTP Cache用法详解
2016/03/18 PHP
PHP基于socket实现客户端和服务端通讯功能
2017/07/13 PHP
PHP实现的只保留字符串首尾字符功能示例【隐藏部分字符串】
2019/03/11 PHP
javascript iframe中打开文件,并检测iframe存在否
2008/12/28 Javascript
方便实用的jQuery checkbox复选框全选功能简单实例
2013/10/09 Javascript
javascript根据像素点取位置示例
2014/01/27 Javascript
基于jQuery实现简单人工智能聊天室
2017/02/10 Javascript
javaScript+turn.js实现图书翻页效果实例代码
2017/02/16 Javascript
Angular4 Select选择改变事件的方法
2018/10/09 Javascript
解决layui动态添加的元素click等事件触发不了的问题
2019/09/20 Javascript
微信小程序8种数据通信的方式小结
2020/02/03 Javascript
vue v-model的用法解析
2020/10/19 Javascript
[01:20]2018DOTA2亚洲邀请赛总决赛战队LGD晋级之路
2018/04/07 DOTA
python执行外部程序的常用方法小结
2015/03/21 Python
在Python中操作列表之list.extend()方法的使用
2015/05/20 Python
详解Python3中的Sequence type的使用
2015/08/01 Python
Python编程实现粒子群算法(PSO)详解
2017/11/13 Python
Python实现pdf文档转txt的方法示例
2018/01/19 Python
基于python代码实现简易滤除数字的方法
2018/07/17 Python
Django csrf 验证问题的实现
2018/10/09 Python
python学生管理系统开发
2019/01/30 Python
python 实现目录复制的三种小结
2019/12/04 Python
Python configparser模块常用方法解析
2020/05/22 Python
Selenium及python实现滚动操作多种方法
2020/07/21 Python
在HTML5中如何使用CSS建立不可选的文字
2014/10/17 HTML / CSS
博士学位自我鉴定范文
2013/12/26 职场文书
社区活动邀请函范文
2014/01/29 职场文书
大学生学雷锋活动总结
2014/06/26 职场文书
水利水电专业自荐信
2014/07/08 职场文书
群众路线剖析材料(四风问题)
2014/10/08 职场文书
2014年信用社工作总结
2014/11/25 职场文书
2015年学校教科室工作总结
2015/07/20 职场文书