详解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 相关文章推荐
输入自动提示搜索提示功能的使用说明:sugggestion.txt
Sep 02 Javascript
js文件Cookie存取值示例代码
Feb 20 Javascript
js select实现省市区联动选择
Apr 17 Javascript
深入理解javascript函数参数与闭包
Dec 12 Javascript
js继承实现方法详解
Dec 16 Javascript
vue自定义filters过滤器
Apr 26 Javascript
关于jquery中attr()和prop()方法的区别
May 28 jQuery
js之切换全屏和退出全屏实现代码实例
Sep 09 Javascript
vue开发移动端底部导航条功能
Apr 08 Javascript
vue webpack build资源相对路径的问题及解决方法
Jun 04 Javascript
Element Alert警告的具体使用方法
Jul 27 Javascript
JavaScript事件委托实现原理及优点进行
Aug 29 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输出时间差函数代码
2013/01/28 PHP
PHP的pcntl多进程用法实例
2015/03/19 PHP
PHP实现在线阅读PDF文件的方法
2015/06/17 PHP
Symfony实现行为和模板中取得request参数的方法
2016/03/17 PHP
php实现微信扫码支付
2017/03/26 PHP
简单JS代码压缩器
2006/10/12 Javascript
js bind 函数 使用闭包保存执行上下文
2011/12/26 Javascript
js 限制数字 js限制输入实现代码
2012/12/04 Javascript
js特效,页面下雪的小例子
2013/06/17 Javascript
JS实现QQ图片一闪一闪的效果小例子
2013/07/31 Javascript
jQuery表格插件datatables用法总结
2014/09/05 Javascript
JS控制网页动态生成任意行列数表格的方法
2015/03/09 Javascript
解决jquery实现的radio重新选中的问题
2015/07/03 Javascript
jQuery焦点图切换特效代码分享
2015/09/15 Javascript
JavaScript实现iframe自动高度调整和不同主域名跨域
2016/02/27 Javascript
jQuery实现的导航下拉菜单效果
2016/07/04 Javascript
jQuery搜索框效果实现代码(百度关键词联想)
2021/02/25 Javascript
jquery 动态增加删除行的简单实例(推荐)
2016/10/12 Javascript
jQuery EasyUi 验证功能实例解析
2017/01/06 Javascript
jquery easyui DataGrid简单示例
2017/01/23 Javascript
Vue 父子组件、组件间通信
2017/03/08 Javascript
js实现简单选项卡制作
2020/08/05 Javascript
[01:33]PWL开团时刻DAY2-开雾与反开雾
2020/10/31 DOTA
python中的break、continue、exit()、pass全面解析
2017/08/05 Python
python 读取文件并把矩阵转成numpy的两种方法
2019/02/12 Python
Django框架创建mysql连接与使用示例
2019/07/29 Python
Python中typing模块与类型注解的使用方法
2019/08/05 Python
Melijoe英国官网:法国儿童时尚网站
2016/11/18 全球购物
缅甸网上购物:Shop.com.mm
2017/12/05 全球购物
Ajxa常见问题都有哪些
2014/03/26 面试题
编辑找工作求职信范文
2013/12/16 职场文书
党支部活动策划方案
2014/08/18 职场文书
上课迟到检讨书300字
2014/10/15 职场文书
教务处干事工作总结
2015/08/14 职场文书
SpringBoot集成Redis,并自定义对象序列化操作
2021/06/22 Java/Android
Nginx stream 配置代理(Nginx TCP/UDP 负载均衡)
2021/11/17 Servers