由浅入深剖析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 相关文章推荐
Lazy Load 延迟加载图片的 jQuery 插件
Feb 06 Javascript
Chrome中模态对话框showModalDialog返回值问题的解决方法
May 25 Javascript
UserData用法总结 lanyu出品
Jul 01 Javascript
ExtJS[Desktop]实现图标换行示例代码
Nov 17 Javascript
jQuery的观察者模式详解
Dec 22 Javascript
浅析Bootstrip的select控件绑定数据的问题
May 10 Javascript
Bootstrap组件学习之导航、标签、面包屑导航(精品)
May 17 Javascript
js正则表达式验证表单【完整版】
Mar 06 Javascript
Three.js获取鼠标点击的三维坐标示例代码
Mar 24 Javascript
Three.js利用orbit controls插件(轨道控制)控制模型交互动作详解
Sep 25 Javascript
jquery实现的分页显示功能示例
Aug 23 jQuery
利用webpack理解CommonJS和ES Modules的差异区别
Jun 16 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中字符集转换iconv函数使用总结
2014/10/11 PHP
laravel excel 上传文件保存到本地服务器功能
2019/11/14 PHP
PHP大文件分割分片上传实现代码
2020/12/09 PHP
[推荐]javascript 面向对象技术基础教程
2009/03/03 Javascript
Jqyery中同等与js中windows.onload的应用
2011/05/10 Javascript
javascript类型转换使用方法
2014/02/08 Javascript
document.forms用法示例介绍
2014/06/26 Javascript
js调用百度地图及调用百度地图的搜索功能
2015/09/07 Javascript
JavaScript 基础函数_深入剖析变量和作用域
2016/05/18 Javascript
浅析Javascript中bind()方法的使用与实现
2016/05/30 Javascript
js弹出框、对话框、提示框、弹窗实现方法总结(推荐)
2016/05/31 Javascript
手机端实现Bootstrap简单图片轮播效果
2016/10/13 Javascript
Bootstrap表格制作代码
2017/03/17 Javascript
详解如何使用Vue2做服务端渲染
2017/03/29 Javascript
Nodejs 复制文件/文件夹的方法
2017/08/24 NodeJs
javascript中可能用得到的全部的排序算法
2020/03/05 Javascript
[59:48]LGD vs IG 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/19 DOTA
Python实现把utf-8格式的文件转换成gbk格式的文件
2015/01/22 Python
Python中函数参数设置及使用的学习笔记
2016/05/03 Python
Python matplotlib的使用并自定义colormap的方法
2018/12/13 Python
PyQt5根据控件Id获取控件对象的方法
2019/06/25 Python
Python-opencv 双线性插值实例
2020/01/17 Python
Matplotlib使用Cursor实现UI定位的示例代码
2020/03/12 Python
解析python 中/ 和 % 和 //(地板除)
2020/06/28 Python
用纯css3实现的图片放大镜特效效果非常不错
2014/09/02 HTML / CSS
美国网上订购鲜花:FTD
2016/09/23 全球购物
墨西哥皇宫度假村预订:Palace Resorts
2018/06/16 全球购物
List、Map、Set三个接口,存取元素时,各有什么特点?
2015/09/27 面试题
什么是makefile? 如何编写makefile?
2012/08/08 面试题
应届毕业生简历自我评价
2014/01/31 职场文书
《秋姑娘的信》教学反思
2014/02/28 职场文书
父亲去世追悼词
2015/06/23 职场文书
教育读书笔记
2015/07/02 职场文书
导游词之绍兴柯岩古镇
2020/01/09 职场文书
Python快速优雅的批量修改Word文档样式
2021/05/20 Python
MySQL 8.0 之不可见列的基本操作
2021/05/20 MySQL