为最近火的一塌糊涂的Angular添一把柴,把一些使用Angular进行数据校验的经验分享一下.说起来前端数据校验的方式千千万万,本质山来讲就是校验显示的方式和时间以及校验触发点的不同.
Angular 对于数据的校验基本上依赖于 HTML5的 Form 表单特性,定义表单标签的校验项即可.
input验证选项
1. 必填
<input type='text' name='cityName' required>
2. 最小长度和最大长度限制
<input type='text' name='cityName' ng-minlength='5' ng-maxlength='10'>
3. 数字,邮件, URL 地址限制
<input type='number' name='cityName'> <input type='email' name='cityName'> <input type='url' name='cityName'>
4. 正则匹配
<input type='text' name='cityName' ng-pattern='[0-9]'>
5. 去空格( Angular )
<input type='text' name='cityName' ng-trim=true>
6. 自定义校验
按照具体的业务,定义数据校验的规则.通常可以编写一个 directive 如果是不会重用的校验,写在 controller 也可以.稍厚一点说这方面你的内容,需要一些铺垫.
angular 属性
Angular为表单控件定义了一些属性,用来表示它们的状态,通过这些状态就可以很方便的针对表单以及其中的控件进行一些操作.目前Angular提供的表单属性并不多,不过可以看到在最新的beta版中已经逐渐增加了新的属性支持,而这些特性我们也可以自己模拟出类似的功能.
1. $pristine 和 $dirty
formName.inputName.$pristine // 用户是否修改了表单,未修改为 true
formName.inputName.$dirty // 用户修改过表单则为 true
2. $valid 和 $invalid
formName.inputName.$valid // 表单内容是否通过校验,通过为 true
formName.inputName.$invalid // 表单内容是否未通过校验,未通过为 true
3. $error
formName.inputName.$error // 表单是否通过验证,验证失败为 true,与$valid 的区别在于可以精确到更具体的错误,比如formName.inputName.$error.maxlength
angular 高级校验特性
在一些情况下,单独的表单验证并不能满足需要,可能有一些特殊的校验,比如关联两个数据等等.通常这类校验都是可复用的,也就是需要一个directive.编写的自定义校验的 directive 并不复杂,首先需要了解几个 Angular 提供的属性和接口.
1. $setValidity()
这个方法可以人为的设置一个表单控件的$valid 以及$ invalid, 也就是说改变表单控件是否通过校验的状态.类似的还有$setDirty()和$setPristine().
ngModel.$setValidity('max-custom', true); // 类似这样的设置就将一个表单控件的状态改变了, ngModel 是 directive 的 link 的第四个参数,$setValidity 的第一个参数可以定义一个标志,第二个参数true 表示通过验证, false 表示未通过
2. $parsers
这个属性解释起来稍微有点抽象,当 ngModel 的值发生变化的时候,Angular 会自己调用 $setViewValue(value),然后 ngModel 的 $parsers 数组中得函数会被逐个调用,当 $parsers[0] 中的方法被调用后执行结果会传递给 $parsers[1],以此类推,这些函数可以对 ngModel 的值进行转换或者通过 $setValidity() 设置表单的合法性.
所以如果我们想实现点什么特殊的校验,可以在 $parsers 中插入一个 function, 然后它会在验证链中被调用,如果不希望数据模型发生更新,让 $parsers 的函数返回值返回 undefined 即可.
.directive('maxMax', function(){
return {
require: 'ngModel',
restrict: 'A',
link: function($scope, iElm, iAttrs, ngModel) {
if(!ngModel) return;
ngModel.$parsers.unshift(function(viewValue){
var num = parseInt(viewValue);
if(num>=0 && num<99){
ngModel.$setValidity('maxMax',true);
return viewValue;
}else{
ngModel.$setValidity('maxMax',false);
return undefined;
}
});
}
};
})
3. $formatters
如果说 $parsers 是当数据模型变化后的一号加工流水线,那 $formatters 就是二号流水线,它在 $parsers 中得函数处理过后,可以进行一些特殊的格式化. 使用的方法与 $parsers 极为类似,不举例了.
自定义校验 directive
比较常见的校验有两种,一个是失去焦点时进行校验的 directive, 另一个是点击了提交之后才开始校验.
(1) 失去焦点后显示校验信息
.directive('ngFocus', function(){
return {
require: 'ngModel',
restrict: 'A',
link: function($scope, iElm, iAttrs, ngModel) {
if(!ngModel) return;
ngModel.$focused = false;
iElm.bind('focus',function(evt){
iElm.addClass('focus-class');
$scope.$apply(function(){
ngModel.$focused = true;
});
}).bind('blur',function(evt){
iElm.removeClass('focus-class');
$scope.$apply(function(){
ngModel.$focused = false;
});
})
}
};
})
在表单的错误信息显示的条件上,加上 formName.inputName.$focused == true 即可.
(2) 点击 submit 按钮之后开启校验
/**
* 表单校验
* form 添加 validation-form 属性,需要检验的标签添加 validation
*/
.directive('validationForm', function(){
return {
restrict: 'A',
link: function($scope, iElm, iAttrs, controller) {
$scope.submitted = false;
$scope.flag = false;
var controls = iElm.find('input[validation]','select[validation]');
iElm.find('button[type="submit"]').on('click',function(e){
addSubmitEvent(e);
});
iElm.parents('.container').find('#'+iAttrs.validationForm).on('click',function(e){
addSubmitEvent(e);
});
var addSubmitEvent = function(e){
$scope.submitted = true;
$.each(controls,function(i,c){
validClass($scope[iAttrs.name][$(c).attr('name')].$valid,$(c));
if(!$scope.flag){
setWatch($(c));
}
})
$scope.flag = true;
}
var validClass = function(nv,control){
if(nv && $scope.submitted){
control.parent().addClass('state-success');
control.parent().removeClass('state-error');
control.parent().next('.note').first().addClass('note-success');
control.parent().next('.note').first().removeClass('note-error');
}else if(!nv && $scope.submitted){
control.parent().addClass('state-error');
control.parent().removeClass('state-success');
control.parent().next('.note').first().addClass('note-error');
control.parent().next('.note').first().removeClass('note-success');
}
}
// 为一个控件开启校验
var setWatch = function(control){
var cValid = iAttrs.name+"."+control.attr('name')+".$valid";
$scope.$watch(cValid,function(nv,ov){
validClass(nv,control);
})
}
}
};
});
Angular 计划在1.3版本以后支持 submitted 这个属性,所以现在还需要我们自己模拟实现以下,上面这个实现的其实还可以更进一步,不过暂时够用就没改,有兴趣的可以自己编写一个更加符合规范的.