Angular在模板驱动表单中自定义校验器的方法


Posted in Javascript onAugust 09, 2017

引言

模板驱动表单相比较响应式表单可以少更少的代码做同样的事情,可也损失了自由度与更易测试,当然很多人并不在乎啦。

所以我相信很多人在编写Angular不自由自主去更倾向于模板驱动表单的写法。

表单最核心的是校验体验,在Angular中简直就是发挥到了极致,比如:required、min、max、pattern 等,这些原本是HTML DOM元素中的表述,而Angular默认实现了一整套的校验指令,比如:required 对应 RequiredValidator。

然后很多时候我们需要一些特殊的校验,比如:数据比较、远程校验等。那在模板驱动表单风格中我们要如何优雅的实现这样一个校验器呢?

一、Angular是如何校验?

一般在编写一个手机文本框可能是这样:

<input [(ngModel)]="user.mobile" #mobile="ngModel" autocomplete="off" type="tel" class="form-control" name="mobile" required maxlength="11">
<div *ngIf="mobile.errors">
  <p *ngIf="mobile.errors.required">手机号必填</p>
  <p *ngIf="mobile.errors.pattern">手机号格式不正确</p>
</div>

以上几行很友好的实现从必填项、格式进行校验,而这一切都是依靠 [(ngModel)] 统一采集,得以只需要利用一个模板引用变量访问到每个校验指令的错误信息。

1、[(ngModel)] 到底做了什么?

在解析这个问题前需要先了解一下 RequiredValidator 是如何定义的。

@Directive({
 providers: [{
   provide: NG_VALIDATORS,
   useExisting: forwardRef(() => RequiredValidator),
   multi: true
  }]
})
export class RequiredValidator {}

只看最核心向 NG_VALIDATORS 标识符注册一个 RequiredValidator 指令。这样就可以使 ngModel 指令中注入 NG_VALIDATORS 后就能得到这个指令对象。

ngModel 我把它简化了一下:

export class NgModel extends NgControl {
  constructor(@Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>) {}
  
  get validator(): ValidatorFn|null {
    // 各种校验并返回结果
  }
}

有关更多ng_model.ts可以深入阅读源代码。

Angular会在每一次表单值变更时,对所有的表单中已经安装的校验器进行一次遍历。

二、编写一个校验器

诚如 required 校验器一样,依然是把自定义校验器挂到 NG_VALIDATORS 当中。假如我们希望手机文本框只能输入 159 开头的一个校验器。

定义Directive

@Directive({
  selector: '[user-mobile]',
  exportAs: 'userMobile',
  providers: [{
    provide: NG_VALIDATORS,
    useExisting: forwardRef(() => UserMobileDirective),
    multi: true
  }]
})
export class UserMobileDirective {}

一个非常普通的指令定义方法,只是多了一个将 UserMobileDirective 注册到 NG_VALIDATORS 标识符当中而已。别问我为什么,一种约定。

export class UserMobileDirective implements Validator {
  validate(c: AbstractControl): { [key: string]: any; } {
    let value: string = c.value || '';
    if (!value.startsWith('159')) {
      return {
        mobile: {
          msg: '手机号必须是159开头',
          actualValue: value
        }
      };
    }
    return null;
  }
}

只需要实现 Validator 接口的 validate 方法即可。

从 c 中获取DOM值,当遇到非 159 开头时,返回一个用于表述消息的对象即可,否则返回一个 null。这个对象会被统一采集在 ngModel.errors 对象下面。故而,只需要在DOM元素加上 user-mobile 指令即可。

<input user-mobile [(ngModel)]="user.mobile" #mobile="ngModel" autocomplete="off" type="tel" class="form-control" name="mobile" id="mobile" required maxlength="11">
<div *ngIf="mobile.errors">
  <p *ngIf="mobile.errors.required">手机号必填</p>
  <p *ngIf="mobile.errors.mobile">{{mobile.errors.mobile.msg}}</p>
</div>

接口还包括一个 registerOnValidatorChange 可选方法,当某些其它外部属性的变更时,允许重新手动触发校验。

三、异步校验器

如果说用户手机校验器需要检查手机是否为黑名单的情况下,正常黑名单数据都存在远程当中。这样情况下需要发送HTTP请求,而这一过程就是异步。

Angular针对这类异步校验有独立的另一个标识符,即:NG_ASYNC_VALIDATORS,而其它代码都是相通的。

@Directive({
  selector: '[user-async]',
  exportAs: 'userAsync',
  providers: [{
    provide: NG_ASYNC_VALIDATORS,
    useExisting: forwardRef(() => UserAsyncDirective),
    multi: true
  }]
})
export class UserAsyncDirective implements Validator {
  validate(c: AbstractControl): Observable<any> {
    return c.valueChanges
        // 去抖
        .debounceTime(300)
        // 抑制重复值
        .distinctUntilChanged()
        // 1、可以使用flatMap进行远程校验
        // .flatMap(value => value)
        // 2、本地模拟判断
        .map((value: string) => {
          if ([ '15900000001', '15900000002' ].includes(value)) {
            return {
              mobile: {
                msg: '手机号为黑名',
                actualValue: value
              }
            }
          }
          return null;
        })
        .first();    
  }
}

除了 NG_ASYNC_VALIDATORS 核心的结构完全没有变动。

而对于 validate 方法返回的是一个 Observable 类型,利用对 valueChanges 的订阅可以制作一些像去抖动作。

而最后必须使用 first() 做为结尾,原因每一次校验,对于结果而言只允许一个。

结论

本章介绍的是如何对模板驱动表单创建自定义校验器,它相比较响应式表单自定义校验器略为复杂一些。但是实际运用中,我们不应该只为某个构建表单风格做一种自定义校验器,应该二者是共存的。

比如上面 159 开头的示例。更合理的编写方式应该是将校验逻辑独立:

export class MyValidators {
  static checkMobile(value: string): ValidationErrors|null {
    return !value.startsWith('159') ? { mobile: { msg: '手机号必须是159开头' } } : null;
  }
}
// 校验器类
export class UserMobileDirective implements Validator {
  validate(c: AbstractControl): { [key: string]: any; } {
    let value: string = c.value || '';
    return MyValidators.checkMobile(value);
  }
}

这样,同一个校验器,不管是模板驱动表单还是响应式表单,都能是通用的。

总结

以上所述是小编给大家介绍的Angular在模板驱动表单中自定义校验器的方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
查询绑定数据岛的表格中的文本并修改显示方式的js代码
Dec 15 Javascript
JavaScript 高效运行代码分析
Mar 18 Javascript
JS 遮照层实现代码
Mar 31 Javascript
JS解决url传值出现中文乱码的另类办法
Apr 08 Javascript
JQuery中对Select的option项的添加、删除、取值
Aug 25 Javascript
js实现音乐播放控制条
Sep 09 Javascript
Vue结合SignalR实现前后端实时消息同步
Sep 19 Javascript
认识jQuery的Promise的具体使用方法
Oct 10 jQuery
vue模式history下在iis中配置流程
Apr 17 Javascript
vue实现直播间点赞飘心效果的示例代码
Sep 20 Javascript
完美解决通过IP地址访问VUE项目的问题
Jul 18 Javascript
浅谈vant组件Picker 选择器选单选问题
Nov 04 Javascript
浅谈react+es6+webpack的基础配置
Aug 09 #Javascript
js中less常用的方法小结
Aug 09 #Javascript
利用JS做网页特效_大图轮播(实例讲解)
Aug 09 #Javascript
基于Vue实例对象的数据选项
Aug 09 #Javascript
react-native之ART绘图方法详解
Aug 08 #Javascript
jQuery Easyui Treegrid实现显示checkbox功能
Aug 08 #jQuery
jQuery EasyUI的TreeGrid查询功能实现方法
Aug 08 #jQuery
You might like
php数组函数序列之asort() - 对数组的元素值进行升序排序,保持索引关系
2011/11/02 PHP
PHP上传图片到数据库并显示的实例代码
2019/12/20 PHP
PHP sdk文档处理常用代码示例解析
2020/12/09 PHP
如何使用jquery控制CSS样式,并且取消Css样式(如背景色,有实例)
2013/07/09 Javascript
Javascript实现图片轮播效果(二)图片序列节点的控制实现
2016/02/17 Javascript
js+html5实现canvas绘制网页时钟的方法
2016/05/21 Javascript
关于js函数解释(包括内嵌,对象等)
2016/11/20 Javascript
微信小程序上滑加载下拉刷新(onscrollLower)分批加载数据(二)
2017/05/11 Javascript
Mongoose实现虚拟字段查询的方法详解
2017/08/15 Javascript
vue数据操作之点击事件实现num加减功能示例
2019/01/19 Javascript
vue动态绑定class的几种常用方式小结
2019/05/21 Javascript
nodejs log4js 使用详解
2019/05/31 NodeJs
js实现秒表计时器
2019/12/16 Javascript
[47:02]2018DOTA2亚洲邀请赛3月29日 小组赛B组 VP VS paiN
2018/03/30 DOTA
[48:05]2018DOTA2亚洲邀请赛 3.31 小组赛 B组 VGJ.T vs VP
2018/03/31 DOTA
Python程序设计入门(2)变量类型简介
2014/06/16 Python
Python多线程编程(二):启动线程的两种方法
2015/04/05 Python
深入理解Python 关于supper 的 用法和原理
2018/02/28 Python
利用Python将文本中的中英文分离方法
2018/10/31 Python
python中类的输出或类的实例输出为这种形式的原因
2019/08/12 Python
YUV转为jpg图像的实现
2019/12/09 Python
Python+OpenCV实现图像的全景拼接
2020/03/05 Python
python求numpy中array按列非零元素的平均值案例
2020/06/08 Python
解决Python3.8运行tornado项目报NotImplementedError错误
2020/09/02 Python
纯HTML5+CSS3制作生日蛋糕(代码易懂)
2016/11/16 HTML / CSS
前端H5 Video常见使用场景简介
2020/08/21 HTML / CSS
Rakuten Kobo台湾:电子书、eReaders和Reading应用程式
2017/11/24 全球购物
艺术设计专业个人求职信
2013/09/21 职场文书
大学生应聘自荐信
2013/10/11 职场文书
2014年社区庆元旦活动方案
2014/03/08 职场文书
关于热爱祖国的演讲稿
2014/05/04 职场文书
自愿离婚协议书范文2014
2014/10/12 职场文书
Oracle使用别名的好处
2022/04/19 Oracle
详解apache编译安装httpd-2.4.54及三种风格的init程序特点和区别
2022/07/15 Servers
阿里面试Nacos配置中心交互模型是push还是pull原理解析
2022/07/23 Java/Android
spring boot实现文件上传
2022/08/14 Java/Android