如何优雅的使用 Angular 表单验证

随便说说,这一节可以跳过

去年参加 ngChine 2018 杭州开发者大会的时候记得有人问我: Worktile 是什么时候开始使用 Angular 的,我说是今年(2018年) 3 月份开始在新模块中使用最新的  Angular 6,他说是不是有点晚了,我当时愣了一下。
其实仔细回想了一下,  Angular 2.0 正式发布也就在 2016-09-14 号,所以也就晚了 1 年多一点点才开始使用而已嘛,再加上 2.0 到 4.0 的过渡或多或少还是有一点点坑的,不是很稳定,此时投入不是特别合适,虽然升级简单,但是还是有很多概念和API需要重新学习,比如 HttpClient,路由等等(在 2.0 的时候我们是有尝试在边缘的一个 Open API Doc 文档站点使用过的,我们一直在关注着最新的 Angular)

所以我觉得 4.0 (也就是 2017-03)之后的 Angular 才真真正正的开始趋于稳定,可以开始尝试学习和使用,再加上经过了 1 年多的社区实践,踩坑,基础生态建设,我觉得今年(2018年)才是企业大规模尝试使用 Angular 最佳时间点,再加上  Angular CLI 以及 @angular/cdk的逐渐强大, 我想说这是一个最好的时代。

另外还有一个原因是 2016 - 2017 年属于我司最艰难的困难时期,压根没有心思考虑切换最新技术栈,所以那 2 年基本上属于埋头做业务。

现在回过头来看 Angular ,在前端框架高速发展的那几年,因为正式版发布迟迟延期,导致市场被后起之秀 React, Vue 等优秀框架占有,好在新的 Angular 足够优秀,足够前瞻,值得花更多的时间投入学习和使用,现在还不算晚。所以慢有一定道理的,因为需要精雕玉琢,衡量未来的趋势等等,但是从完美支持 TypeScript,RxJS 这 2 点来说,Angular 的确走在了前面。

回归正题,说到表单,我认为一个强大表单应该包含下面3部分功能

1.收集用户的输入的表单数据,在 Angular 中通过 ngModel 实现双向绑定非常方便;
2.通过各种验证器验证表单元素输入的数据是否合法,Angular 内置了常用的验证器(required、pattern、email,min,max,minLength,maxLength);
3.验证后给予用户反馈,比如验证不通过给予错误的提示信息。

我觉得 Angular 的表单无疑是三大框架中最强大的,没有之一,而且是官方原生提供支持和维护,提到 Angular 的表单肯定要说下 Angular.js 的表单,其实 Angular 的表单基本上继承了 Angular.js 表单的所有功能,同时比 Angular.js 更强大,API 更友好。

另外说下本文不是普及 Angular Form 表单的基本知识的,如果有不了解的可以看 angular forms guide ,因为官方文档已经写的特别好了。

那么 Angular 的表单和 Angular.js 相比到底有哪些改进呢?

1.自定义 ngModel
在 Angular.js 中 ng-model 只能用于 input,select 等内置的 HTML 表单元素,如果是一个自定义的 select 框(div),可能就无法使用自带的 required 等验证器了

但是可以通过猥琐的方式处理,比如加一个隐藏的原生 HTML 表单元素,这个元素上绑定的 ng-model 和自定义的 select 框的 model 是一样的,然后通过这个隐藏元素是否验证通过去控制自定义 Select 的验证样式

那么在 Angular 中可以很方便让任何自定义的组件使用 ngModel 和 内置的验证器,只要你的自定义组件实现 ControlValueAccessor 接口,同时在组件的 providers 中加上 NG_VALUE_ACCESSOR 的 provider 即可,具体如何实现一个自定义的支持 ngModel 组件自行搜索下,官方文档好像没有找到相关介绍, 附一个 stackoverflow question

2.结构型指令内部的表单元素自动识别
在 Angular.js 中如果有 ng-if 之类的动态指令,内部的表单元素不会自动追加到 Form 上,必须通过扩展一个自定义指令 dynamicFormControl 去手动追加到 ngForm 上,但是在 Angular 中不需要用户自己去处理,只要元素被渲染,会自动附加到  ngForm Controls 中。

3.响应式表单
Angular 中除了模板驱动表单外,还新增了响应式表单,让用户多了一份选择,在某些复杂的场景下,响应式表单会更有优势。

4.动态表单支持更好
在 Angular 中不管是模板驱动表单还是响应式表单,对于动态创建表单的支持都很好,可以轻松的通过 [attr.name]="formName"[name]="formName" 实现动态表单元素的创建。如果有复杂的验证器,那么使用 响应式表单 会更好。

5.模板驱动表单验证器支持属性绑定,动态控制是否需要验证
如果一个表单元素(比如说用户名)是否为必填不是确定的,而是动态设置的, 在 Angular 中可以通过属性绑定 [required]="isRequired" 非常方便的控制,我看了下 Angular.js 的源码现在也是支持的,不知道是我以前没有发现呢还是之后的版本加上的功能。

6.支持异步验证器
如果要验证用户名输入是否已经存在,就需要请求 API 远程验证,那么这个验证就是一个异步,如果验证不支持就会导致验证结果没有返回的时候就直接提交表单了。如果支持异步会更加的完善。

通过上述的几点来看, Angular 表单基本已经完美了,但是

我们还需要让验证错误提示更加简单

回头再看下开头的介绍的表单应该包含下面3部分功能
1.收集用户的输入的表单数据; 这个基本上 ngModel 双向绑定的语法糖已经简化的不能再简化了,当然使用响应式表单连 ngModel 也可以不写;
2.内置的验证器满足大部分场景,但是还是会有很多常用的验证器官方没有提供,比如 重复验证,远程唯一性验证等等,@Nightapes/ngx-validators@gangachris/ng-validators 这2个第三方库扩展了很多,即使不满足自己扩展也很简单;
3.验证后给予用户反馈,验证不通过给予错误的提示信息。对于这个错误提示信息,每个产品每个用户都会有不一样的需求,Angular 把可以做到的都做到了,都自动追加了 ng-invalidng-validng-touched 等 class,还有就是哪些元素哪个验证器验证失败都可以从 ngModel 和 ngForm 方便的获取到,错误提示只能交给用户自己去处理。

对于验证错误提示,手动写错误提示的模版会很啰嗦,写模版本身也没什么,怕就怕哪天设计师改需求了,原有的提示方式换了一个新的方式,那整个系统都需要挨个替换,有追求的程序员最怕的就是做重复没有含量的工作,而且有时候还无法通过批量替换完成,所以在使用 Angular.js 1.x 的时候我就封装了一个表单验证库 angular-w5c-validator,刚开始发布的时候功能比较简单,后来有人提各种 Issue,逐渐改善,我觉得这个验证库对于很多人来说还是有帮助的,至少我觉得是更优雅的处理了各种错误提示,star 不多,但是证明了这个封装还是有一定价值的。

那么我们即使现在升级到了 Angular ,也面临着错误提示如何处理的问题,当然也有些类库处理了相关问题,但是好像都没有找到特别好用的。

ngx-errors 还是手写模版,只是简化了写法。

ng-zorro-antd 组件库关于表单组件对错误验证提示也做了很多工作,但是还是需要手写模版配置。

既然没有相关的类库符合我们的需求,那么显然就需要自己造轮子,所以我们去年在升级 Angular 时就按照我们的方式在组件库的 Form 表单模块加上了和 Angular.js angular-w5c-validator 类似的 API,得益于 Angular 框架的优秀,造起轮子特别简单。

内部的组件库暂时还无法开源出去让更多人使用,但是的但是

ngx-validator 已经可以开始使用了

所以这周我单独抽离了表单验证功能为一个独立的组件 ngx-validator , 如果你也再为表单验证错误提示苦恼,也在寻找一种更优雅的错误处理方式,希望我的这个库可以帮助到你或者给你一个启发。

ngx-validator Demo 示例,点击直接查看演示

最后的最后感谢你耐心阅读到此,这篇博客已经计划了 3 个多月了,因为工作繁忙一直没有时间,这周末下了一个狠心,必须完成!已经被儿子打扰多次,还有就是 ngx-validator 目前基本的功能已经完成,后期还有很多增强的验证器,测试需要补充,还不是特别完善,欢迎大家提宝贵意见!