欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详解Angular Reactive Form 表单验证

程序员文章站 2022-11-26 14:56:39
本文我们将介绍 reactive form 表单验证的相关知识,具体内容如下: 使用内建的验证规则 动态调整验证规则 自定义验证器 自定义验证器...

本文我们将介绍 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));
 }
}

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