由浅入深剖析Angular表单验证


Posted in Javascript onJuly 14, 2016

最近手上维护的组件剩下的BUG都是表单验证,而且公司的表单验证那块代码经历的几代人,里面的逻辑开始变得不清晰,而且代码结构不是很angular。

是很有必要深入了解表单验证。

<body ng-controller="MainController">
<form name="form" novalidate="novalidate">
<input name="text" type="email" ng-model="name">
</form>
</body>

ngModel是angular的黑魔法,实现双向绑定,当name的值变化的时候,input的value也会跟着变化。

当用户在input修改value的时候,name的值也会跟着变化。

novalidate="novalidate"的目的是去除系统自带的表单验证。

上面那段代码解析完,angular会在MainController的$scope下面生成一个变量"form",$scope.form,这个变量的名称跟html中form.name一致。

而$scope.form.text为文本输入框的Model,继承自ngModelController。

其中$scope.form实例自FormController。其内容为:

由浅入深剖析Angular表单验证

文本输入框的Model(也就是$scope.form.text)为:

由浅入深剖析Angular表单验证

其中$dirty/$pristine,$valid/$invalid,$error为常用属性。尤其是$error。

最简单的表单验证:

了解了form和输入框,就可以先撸个最简单的显示错误的指令。

html内容如下:

<form name="form" novalidate="novalidate">
<input name="text" type="email" ng-model="name" error-tip>
</form>

指令代码如下:

// 当输入框出错,就显示错误
directive("errorTip",function($compile){
return {
restrict:"A",
require:"ngModel",
link:function($scope,$element,$attrs,$ngModel){
//创建子scope
var subScope = $scope.$new(),
//错误标签的字符串,有错误的时候,显示错误内容
tip = '<span ng-if="hasError()">{{errors() | json}}</span>';
//脏,而且无效,当然属于错误了
$scope.hasError = function(){
return $ngModel.$invalid && $ngModel.$dirty;
} //放回ngModel的错误内容,其实就是一个对象{email:true,xxx:true,xxxx:trie}
$scope.errors = function(){
return $ngModel.$error;
}
//编译错误的指令,放到输入框后面
$element.after($compile(tip)(subScope));
}
}
});

先看看执行结果:

输入无效的邮箱地址的时候:

由浅入深剖析Angular表单验证

输入正确的邮箱地址的时候:

由浅入深剖析Angular表单验证

errorTip指令一开始通过 require:"ngModel" 获取ngModelController。然后创建用于显示错误的元素到输入框。

这里使用了$compile,$compile用于动态编译显示html内容的。

当有错误内容的时候,错误的元素就会显示。

为什么subScope可以访问hasError和errors方法?

因为原型链。看下图就知道了。

由浅入深剖析Angular表单验证

自定义错误内容

好了,很明显现在的表单验证是不能投入使用的,我们必须自定义显示的错误内容,而且要显示的错误不仅仅只有一个。

显示多个错误使用ng-repeat即可,也就是把"errorTip"指令中的

tip = '<span ng-if="hasError()">{{errors() | json}}</span>';

改成:

tip = '<ul ng-if="hasError()" ng-repeat="(errorKey,errorValue) in errors()">' +
'<span ng-if="errorValue">{{errorKey | errorFilter}}</span>' +
'</ul>';

其中errorFilter是一个过滤器,用于自定义显示错误信息的。过滤器其实是个函数。

其代码如下:

.filter("errorFilter",function(){
return function(input){
var errorMessagesMap = {
email:"请输入正确的邮箱地址",
xxoo:"少儿不宜"
}
return errorMessagesMap[input];
}
});

结果如下:

由浅入深剖析Angular表单验证

好了,到这里就能够处理“简单”的表单验证了。对,简单的。我们还必须继续深入。

自定义表单验证!

那我们就来实现一个不能输入“帅哥”的表单验证吧。

指令如下:

.directive("doNotInputHandsomeBoy",function($compile){
return {
restrict:"A",
require:"ngModel",
link:function($scope,$element,$attrs,$ngModel){
$ngModel.$parsers.push(function(value){
if(value === "帅哥"){
//设置handsome为无效,设置它为无效之后,$error就会变成{handsome:true}
$ngModel.$setValidity("handsome",false);
}
return value;
})
}
}
})

结果如下:

由浅入深剖析Angular表单验证

这里有两个关键的东西,$ngModel.$parsers和$ngModel.$setValidity.

$ngModel.$parsers是一个数组,当在输入框输入内容的时候,都会遍历并执行$parsers里面的函数。

$ngModel.$setValidity("handsome",false);设置handsome为无效,会设置$ngModel.$error["handsome"] = true;

也会设置delete $ngModel.$$success["handsome"],具体可以翻翻源码。

这里我总结一下流程。

-->用户输入

-->angular执行所有$parsers中的函数

-->遇到$setValidity("xxoo",false);那么就会把xxoo当做一个key设置到$ngModel.$error["xxoo"]

-->然后errorTip指令会ng-repeat $ngModel.$error

-->errorFilter会对错误信息转义

-->最后显示错误的信息

自定义输入框的显示内容

很多时候开发,不是简简单单验证错误显示错误那么简单。有些时候我们要格式化输入框的内容。

例如,"1000"显示成"1,000"

"hello"显示成"Hello"

现在让我们实现自动首字母大写。

源码如下:

<form name="form" novalidate="novalidate">
<input name="text" type="text" ng-model="name" upper-case>
</form> 
.directive("upperCase",function(){
return {
restrict:"A",
require:"ngModel",
link:function($scope,$element,$attrs,$ngModel){
$ngModel.$parsers.push(function(value){
var viewValue;
if(angular.isUndefined(value)){
viewValue = "";
}else{
viewValue = "" + value;
}
viewValue = viewValue[0].toUpperCase() + viewValue.substring(1);
//设置界面内容
$ngModel.$setViewValue(viewValue);
//渲染到界面上,这个函数很重要
$ngModel.$render();
return value;
})
}
}
});

由浅入深剖析Angular表单验证

这里我们使用了$setViewValue和$render,$setViewValue设置viewValue为指定的值,$render把viewValue显示到界面上。

很多人以为使用了$setViewValue就能更新界面了,没有使用$render,最后不管怎么搞,界面都没刷新。

如果只使用了$ngModel.$parsers是不够的,$parsers只在用户在输入框输入新内容的时候触发,还有一种情况是需要重新刷新输入框的内容的:

那就是双向绑定,例如刚才的输入框绑定的是MainController中的$scope.name,当用户通过其他方式把$scope.name改成"hello",输入框中看不到首字母大写。

这时候就要使用$formatters,还是先看个例子吧.

<body ng-controller="MainController">
<form name="form" novalidate="novalidate">
<button ng-click="random()">随机</button>
<input name="text" type="text" ng-model="name" upper-case>
</form>
</body>

MainController的内容:

angular.module("app", [])
.controller("MainController", function ($scope, $timeout) {
$scope.random = function(){
$scope.name = "hello" + Math.random();
}
})

够简单吧,点击按钮的时候,$scope.name变成hello开头的随机内容.

由浅入深剖析Angular表单验证

很明显,hello的首字母没大写,不是我们想要的内容。

我们修改下指令的内容:

.directive("upperCase",function(){
return {
restrict:"A",
require:"ngModel",
link:function($scope,$element,$attrs,$ngModel){
$ngModel.$parsers.push(function(value){
var viewValue = upperCaseFirstWord(handleEmptyValue(value));
//设置界面内容
$ngModel.$setViewValue(viewValue);
//渲染到界面上,这个函数很重要
$ngModel.$render();
return value;
})
//当过外部设置modelValue的时候,会自动调用$formatters里面函数
$ngModel.$formatters.push(function(value){
return upperCaseFirstWord(handleEmptyValue(value));
})
//防止undefined,把所有的内容转换成字符串
function handleEmptyValue(value){
return angular.isUndefined(value) ? "" : "" + value;
}
//首字母大写
function upperCaseFirstWord(value){
return value.length > 0 ? value[0].toUpperCase() + value.substring(1) : "";
}
}
}
});

总结一下:

由浅入深剖析Angular表单验证

1.
-->用户在输入框输入内容

-->angular遍历$ngModel.$parsers里面的函数转换输入的内容,然后设置到$ngModel.$modelValue

-->在$ngModel.$parsers数组中的函数里,我们修改了$ngModel.$viewValue,然后$ngMode.$render()渲染内容。

2.

-->通过按钮生成随机的字符串设置到name

-->每次脏检测都会判断name的值是否跟$ngModel.$modelValue不一致(这里是使用$watch实现的),不一致就反序遍历$formaters里面的所有函数并执行,把最终返回值赋值到$ngModel.$viewValue

-->刷新输入框内容

“自定义输入框的显示内容”的例子能不能优化?

为什么要优化?

原因很简单,为了实现“自定义内容”,使用了$parsers和$formatters,其实两者的内容很像!这一点很关键。

怎么优化?

使用$ngModel.$validators。

好,现在把例子再改一下。

.directive("upperCase",function(){
return {
restrict:"A",
require:"ngModel",
link:function($scope,$element,$attrs,$ngModel){
//1.3才支持,不管手动输入还是通过其他地方更新modelValue,都会执行这里
$ngModel.$validators.uppercase = function(modelValue,viewValue){
var viewValue = upperCaseFirstWord(handleEmptyValue(modelValue));
//设置界面内容
$ngModel.$setViewValue(viewValue);
//渲染到界面上,这个函数很重要
$ngModel.$render();
//返回true,表示验证通过,在这里是没啥意义
return true;
}
//防止undefined,把所有的内容转换成字符串
function handleEmptyValue(value){
return angular.isUndefined(value) ? "" : "" + value;
}
//首字母大写
function upperCaseFirstWord(value){
return value.length > 0 ? value[0].toUpperCase() + value.substring(1) : "";
}
}
}
})

代码简洁了很多,$ngModel.$validators在1.3以上的版本才支持。

$ngModel.$validators.uppercase函数的返回值如果是false,那么$ngModel.$error['uppercase']=true。这一点跟$ngModel.$setValidity("uppercase",false)差不多。

Javascript 相关文章推荐
探讨在JQuery和Js中,如何让ajax执行完后再继续往下执行
Jul 09 Javascript
js实现div闪烁原理及实现代码
Jun 24 Javascript
jQuery实现“扫码阅读”功能
Jan 21 Javascript
个人总结的一些JavaScript技巧、实用函数、简洁方法、编程细节
Jun 10 Javascript
javascript中的正则表达式使用详解
Aug 30 Javascript
AngularJS 模块化详解及实例代码
Sep 14 Javascript
浅谈react.js 之 批量添加与删除功能
Apr 17 Javascript
Javascript ES6中数据类型Symbol的使用详解
May 02 Javascript
JavaScript面试技巧之数组的一些不low操作
Mar 22 Javascript
详解javascript中的Error对象
Apr 25 Javascript
Vue.js页面中有多个input搜索框如何实现防抖操作
Nov 04 Javascript
vue中注册自定义的全局js方法
Nov 15 Javascript
jQuery 3.0十大新特性最终版发布
Jul 14 #Javascript
js css+html实现简单的日历
Jul 14 #Javascript
javascript运算符——位运算符全面介绍
Jul 14 #Javascript
Angular.js 实现数字转换汉字实例代码
Jul 14 #Javascript
JavaScript——DOM操作——Window.document对象详解
Jul 14 #Javascript
window.close(); 关闭浏览器窗口js代码的总结介绍
Jul 14 #Javascript
Jquery实现遮罩层的简单实例(就是弹出DIV周围都灰色不能操作)
Jul 14 #Javascript
You might like
PHP+AJAX实现无刷新注册(带用户名实时检测)
2006/12/02 PHP
php在服务器执行exec命令失败的解决方法
2012/03/03 PHP
PHP学习笔记之字符串编码的转换和判断
2014/05/22 PHP
thinkphp数据查询和遍历数组实例
2014/11/28 PHP
thinkphp中字符截取函数msubstr()用法分析
2016/01/09 PHP
PHP实现的操作数组类库定义与用法示例
2019/05/24 PHP
js利用Array.splice实现Array的insert/remove
2009/01/13 Javascript
跨域表单提交状态的变相判断代码
2009/11/12 Javascript
jquery.jstree 增加节点的双击事件代码
2010/07/27 Javascript
jquery indexOf使用方法
2013/08/19 Javascript
JavaScript操作cookie类实例
2015/03/31 Javascript
JS中绑定事件顺序(事件冒泡与事件捕获区别)
2017/01/24 Javascript
原生JS实现圣旨卷轴展开效果
2017/03/06 Javascript
微信小程序购物车、父子组件传值及calc的注意事项总结
2018/11/14 Javascript
vue3.0中使用element的完整步骤
2021/03/04 Vue.js
Python基于pygame实现图片代替鼠标移动效果
2015/11/11 Python
Python中防止sql注入的方法详解
2017/02/25 Python
利用Python进行异常值分析实例代码
2017/12/07 Python
python PyTorch参数初始化和Finetune
2018/02/11 Python
python写一个md5解密器示例
2018/02/23 Python
浅谈pandas中Dataframe的查询方法([], loc, iloc, at, iat, ix)
2018/04/10 Python
PyTorch上搭建简单神经网络实现回归和分类的示例
2018/04/28 Python
python爬虫URL重试机制的实现方法(python2.7以及python3.5)
2018/12/18 Python
Django项目后台不挂断运行的方法
2019/08/31 Python
python网络编程之多线程同时接受和发送
2019/09/03 Python
python 实现多线程下载视频的代码
2019/11/15 Python
OpenCV里的imshow()和Matplotlib.pyplot的imshow()的实现
2019/11/25 Python
python打开文件的方式有哪些
2020/06/29 Python
HTML5不支持frameset的两种解决方法
2016/11/14 HTML / CSS
丝芙兰加拿大官方网站:SEPHORA加拿大
2018/11/20 全球购物
为什么说Ruby是一种真正的面向对象程序设计语言
2012/10/30 面试题
工商学院毕业生自荐信
2013/11/12 职场文书
《李广射虎》教学反思
2014/04/27 职场文书
给上级领导的感谢信
2015/01/22 职场文书
预防艾滋病宣传活动总结
2015/05/09 职场文书
一篇文章告诉你如何实现Vue前端分页和后端分页
2022/02/18 Vue.js