强大的 Angular 表单验证

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

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

  1. 虽然我们已经搭建了整个布局,但是还没有实现我们的最终目的:实现自定义的表单验证
    接下来我们创建一个正则验证器,新建文件 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: ''
  };

大家可自行尝试一下!

  1. 完整代码:
    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;
      }
    }

本文结束,感谢阅读!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • HTML表单 在HTML中,表单是 ... 之间元素的集合,它们允许访问者输入文本、选择选项、操作对象等等,然后将...
    兰山小亭阅读 3,404评论 2 14
  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,703评论 2 17
  • 细说 Angular 2+ 的表单(一):模板驱动型表单 响应式表单 响应式表单乍一看还是很像模板驱动型表单的,但...
    接灰的电子产品阅读 3,325评论 5 22
  • 动态表单(React Forms)是一种动态构建表单的技术,用于解决有时候手动编写和维护表单所需工作量和时间会过大...
    阿狸不歌阅读 7,698评论 0 2