最近手上维护的组件剩下的BUG都是表单验证,并且公司的表单验证那块代码经历的几代人,里面的逻辑开始变得不清晰,并且代码结构不是很angular。html
是颇有必要深刻了解表单验证。json
入门以前,我以为应该先了解angular内置的表单验证有哪些:数组
1,必填项app
验证某个表单是否已经填写,只要在元素上标记required便可:dom
<input type="text" required>函数
2,最小长度优化
验证表单输入框的内容是否大于某个最小值。ui
<input ng-minlength="5">url
3,最大长度spa
验证表单输入框的内容是否小于某个最大值、
<input ng-maxlength="5">
4,匹配正则
确保输入的内容匹配某个正则。
<input ng-pattern="[a-zA-Z]">
5,电子邮件
验证内容是不是电子邮件。
<input type="email">
6,数字
验证输入内容是不是数字.
<input type="number">
7,URL
验证输入内容是不是URL。
<input type="url">
了解内置的表单验证的颇有必要的,能够避免重复开发。
接着就能够看看最简单的表单验证例子。
<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。其内容为:
文本输入框的Model(也就是$scope.form.text)为:
其中$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)); } } });
输入无效的邮箱地址的时候:
输入正确的邮箱地址的时候:
errorTip指令一开始经过 require:"ngModel" 获取ngModelController。而后建立用于显示错误的元素到输入框。
这里使用了$compile,$compile用于动态编译显示html内容的。具体原理能够看这里:http://www.cnblogs.com/accordion/p/5156553.html.
当有错误内容的时候,错误的元素就会显示。
为何subScope能够访问hasError和errors方法?
由于原型链。看下图就知道了。
好了,很明显如今的表单验证是不能投入使用的,咱们必须自定义显示的错误内容,并且要显示的错误不只仅只有一个。
显示多个错误使用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]; } });
结果以下:
好了,到这里就可以处理“简单”的表单验证了。对,简单的。咱们还必须继续深刻。
那咱们就来实现一个不能输入“帅哥”的表单验证吧。
指令以下:
.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; }) } } })
结果以下:
这里有两个关键的东西,$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; }) } } });
这里咱们使用了$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开头的随机内容.
很明显,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) : ""; } } } });
“自定义输入框的显示内容”的例子能不能优化?
为何要优化?
缘由很简单,为了实现“自定义内容”,使用了$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)差很少。
内容不完整的话,请拍砖,我继续加。
晚点补上源码剖析部分。