强大的 Angular 表单验证

7,958 阅读6分钟

Angular 支持非常强大的内置表单验证,maxlength、minlength、required 以及 pattern。使用 Angular 的内置表单校验能够完成绝大多数的业务场景的校验需求,但有时我们还需要实现更为复杂的表单校验功能,这时可以使用 Angular 提供的表单自定义校验(Custom Validator)。下面,我们就来了解一下如何使用 Angular 的自定义表单校验

效果图:


QQ截图20170428193800.png
  1. 首先,来创建我们的注册组件(register),并在模版中显示一个简单的表单

    <h3 class="text-center">注册</h3>
    
    <form>
    
     <div class="form-group">
       <label for="username">用户名:</label>
       <input type="text" id="username" class="form-control" >
     </div>
    
    </form>

    为了使表单看上去能够漂亮一些,在 index.html 中引入 bootstrap 样式文件:

    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
  2. 接下来确定我们的验证需求:

     我们希望用户名只能包含数字、字母和下划线,且不能以下划线开头
    • 首先为 form 标签添加 formGroup 指令:
      <form [formGroup]="registerForm" >
    • 并且为 input 标签添加 formControlName 指令:
      <input formControlName="username" type="text" 
        id="username" class="form-control" >
  3. 在代码中定义验证规则:
    从内置表单模块中导入以下类:

    import { FormBuilder, FormGroup, Validators } from '@angular/forms';

    其中:

          1. formBuilder 用来构建表单数据
          2. formGroup 表示表单类型
          3. Validators 包含了表单内置的验证规则,如: Validators.required

    定义表单属性

    registerForm: FormGroup;

    定义表单验证不通过时每一项显示的错误消息(目前我们只有 username )

     formErrors = {
       username: ''
     };

    为每一项验证规则定义验证失败时的说明文字(表单控件可能有多条验证规则,由不通过的验证说明构成一条错误消息)

     validationMessage = {
       'username': {
         'minlength': '用户名长度最少为3个字符',
         'maxlength': '用户名长度最多为10个字符',
         'required': '请填写用户名'
       }
     };

    在构造函数中添加 fb 属性用来构建表单

    constructor(private fb: FormBuilder) { }

    添加构建表单的方法

     buildForm(): void {
     // 通过 formBuilder构建表单
     this.registerForm = this.fb.group({
       /* 为 username 添加3项验证规则:
        * 1.必填, 2.最大长度为10, 3.最小长度为3
        * 其中第一个空字符串参数为表单的默认值
       */
       'username': [ '', [
         Validators.required,
         Validators.maxLength(10),
         Validators.minLength(3)
       ]]
     });

    接下来我们添加一个方法用来更新错误信息

     onValueChanged(data?: any) {
       // 如果表单不存在则返回
       if (!this.registerForm) return;
       // 获取当前的表单
       const form = this.registerForm;
    
       // 遍历错误消息对象
       for (const field in this.formErrors) {
         // 清空当前的错误消息
         this.formErrors[field] = '';
         // 获取当前表单的控件
         const control = form.get(field);
    
         // 当前表单存在此空间控件 && 此控件没有被修改 && 此控件验证不通过
         if (control && control.dirty && !control.valid) {
           // 获取验证不通过的控件名,为了获取更详细的不通过信息
           const messages = this.validationMessage[field];
           // 遍历当前控件的错误对象,获取到验证不通过的属性
           for (const key in control.errors) {
             // 把所有验证不通过项的说明文字拼接成错误消息
             this.formErrors[field] += messages[key] + '\n';
           }
         }
       }
     }

    下面只需要在表单构建结束后初始化错误消息,并且在每次表单数据更改时更新错误消息就可以了
    在 buildForm 方法中添加如下代码

     // 每次表单数据发生变化的时候更新错误信息
     this.registerForm.valueChanges
       .subscribe(data => this.onValueChanged(data));
    
     // 初始化错误信息
     this.onValueChanged();

    此时,我们已经很好的控制了错误信息,下面只需要在表单模版中添加错误信息的显示就可以了
    在 input 标签下方添加如下代码:

     <div *ngIf="formErrors.username" 
       class="showerr alert alert-danger" >{{ formErrors.username }}</div>

    添加如下代码到表单模版的 css 中:

     form {
       width: 90%;
       max-width: 45em;
       margin: auto;
     }
    
     .showerr {
       white-space: pre-wrap;
     }

    现在我们就可以尝试运行了,在代码不报错的情况下已经能够看到非常好的效果了
    如果代码报错或没有出现想象中的效果则可以参照本文结尾的完整代码进行修改

  4. 虽然我们已经搭建了整个布局,但是还没有实现我们的最终目的:实现自定义的表单验证
    接下来我们创建一个正则验证器,新建文件 validate-register.ts :

     import { ValidatorFn, AbstractControl } from '@angular/forms';
    
     export function validateRex(type: string, validateRex: RegExp): ValidatorFn {
       return (control: AbstractControl): {[key: string]: any} => {
         // 获取当前控件的内容
         const str = control.value;
         // 设置我们自定义的验证类型
         const res = {};
         res[type] = {str}
         // 如果验证通过则返回 null 否则返回一个对象(包含我们自定义的属性)
         return validateRex.test(str) ? null : res;
       }
     }

    下面我们在代码中导入此函数:
    import { validateRex } from './validate-register';
    修改 validationMessage 属性为:

     // 为每一项表单验证添加说明文字
     validationMessage = {
       'username': {
         'minlength': '用户名长度最少为3个字符',
         'maxlength': '用户名长度最多为10个字符',
         'required': '请填写用户名',
         'notdown': '用户名不能以下划线开头',
         'only': '用户名只能包含数字、字母、下划线'
       }
     };

    修改 buildForm 方法:

     // 通过 formBuilder构建表单
     this.registerForm = this.fb.group({
       /* 为 username 添加 5 项验证规则:
        * 1.必填, 2.最大长度为10, 3.最小长度为3, 4.不能以下划线开头, 5.只能包含数字、字母、下划线
        * 其中第一个空字符串参数为表单的默认值
       */
       'username': [ '', [
         Validators.required,
         Validators.maxLength(10),
         Validators.minLength(3),
         validateRex('notdown', /^(?!_)/),
         validateRex('only', /^[1-9a-zA-Z_]+$/)
       ]]
     });

    OK ! 大功告成了,赶紧运行代码尝试一下吧,我们可以随时添加各种验证规则,只需要修改 validationMessage 属性和 buildForm 方法即可!
    如果添加多个表单控件的话还需要修改 formErrors,例如添加 password 控件则修改 formErrors 为

    formErrors = {
     username: '',
     password: ''
    };

    大家可自行尝试一下!

  5. 完整代码:
    register.component.html:

     <h3 class="text-center">注册</h3>
    
     <form [formGroup]="registerForm" >
    
       <div class="form-group">
         <label for="username">用户名:</label>
    
         <input formControlName="username"
           type="text" id="username" #username
           class="form-control" >
         <div *ngIf="formErrors.username" class="showerr alert alert-danger" >{{ formErrors.username }}</div>
       </div>
    
     </form>

    register.component.css:

     form {
       width: 90%;
       max-width: 45em;
       margin: auto;
     }
    
     .showerr {
       white-space: pre-wrap;
     }

    register.component.ts:

     import { Component, OnInit } from '@angular/core';
     import { FormBuilder, FormGroup, Validators } from '@angular/forms';
     import { validateRex } from './validate-register';
    
     @Component({
       selector: 'app-register',
       templateUrl: './register.component.html',
       styleUrls: ['./register.component.css']
     })
     export class RegisterComponent implements OnInit {
    
       // 定义表单
       registerForm: FormGroup;
    
       // 表单验证不通过时显示的错误消息
       formErrors = {
         username: ''
       };
    
       // 为每一项表单验证添加说明文字
       validationMessage = {
         'username': {
           'minlength': '用户名长度最少为3个字符',
           'maxlength': '用户名长度最多为10个字符',
           'required': '请填写用户名',
           'notdown': '用户名不能以下划线开头',
           'only': '用户名只能包含数字、字母、下划线'
         }
       };
    
       // 添加 fb 属性,用来创建表单
       constructor(private fb: FormBuilder) { }
    
       ngOnInit() {
         // 初始化时构建表单
         this.buildForm();
       }
    
       // 构建表单方法
       buildForm(): void {
         // 通过 formBuilder构建表单
         this.registerForm = this.fb.group({
           /* 为 username 添加3项验证规则:
            * 1.必填, 2.最大长度为10, 3.最小长度为3, 4.不能以下划线开头, 5.只能包含数字、字母、下划线
            * 其中第一个空字符串参数为表单的默认值
           */
           'username': [ '', [
             Validators.required,
             Validators.maxLength(10),
             Validators.minLength(3),
             validateRex('notdown', /^(?!_)/),
             validateRex('only', /^[1-9a-zA-Z_]+$/)
           ]]
         });
    
         // 每次表单数据发生变化的时候更新错误信息
         this.registerForm.valueChanges
           .subscribe(data => this.onValueChanged(data));
    
         // 初始化错误信息
         this.onValueChanged();
       }
    
       // 每次数据发生改变时触发此方法
       onValueChanged(data?: any) {
         // 如果表单不存在则返回
         if (!this.registerForm) return;
         // 获取当前的表单
         const form = this.registerForm;
    
         // 遍历错误消息对象
         for (const field in this.formErrors) {
           // 清空当前的错误消息
           this.formErrors[field] = '';
           // 获取当前表单的控件
           const control = form.get(field);
    
           // 当前表单存在此空间控件 && 此控件没有被修改 && 此控件验证不通过
           if (control && control.dirty && !control.valid) {
             // 获取验证不通过的控件名,为了获取更详细的不通过信息
             const messages = this.validationMessage[field];
             // 遍历当前控件的错误对象,获取到验证不通过的属性
             for (const key in control.errors) {
               // 把所有验证不通过项的说明文字拼接成错误消息
               this.formErrors[field] += messages[key] + '\n';
             }
           }
         }
       }
    
     }

    validate-register.ts:

     import { ValidatorFn, AbstractControl } from '@angular/forms';
    
     export function validateRex(type: string, validateRex: RegExp): ValidatorFn {
       return (control: AbstractControl): {[key: string]: any} => {
         // 获取当前控件的内容
         const str = control.value;
         // 设置我们自定义的严重类型
         const res = {};
         res[type] = {str}
         // 如果验证通过则返回 null 否则返回一个对象(包含我们自定义的属性)
         return validateRex.test(str) ? null : res;
       }
     }

本文结束,感谢阅读!