angular的form体系,为我们做表单验证提供了很大的便利.
angular 源码中,ngModelDirective 会在preLink里找到它从属的form,然后调用form的addControl方法把自己注册进去。
根据form的addControl方法,我们可以通过 form.元素的name 访问到元素的一些信息,可以自己打印出来看看。Form在 $setPristine 或其他操作时,会依次调用自己controls里的那些ctrl上的相应方法。
比如 form.$commitViewValue 依次调用了 control.$commitViewValue 方法,而ngModelController里的 $commitViewValue 方法会调用 $$parseAndValidate ,这个方法里循环调用了我们稍后会用到的 parsers 里的所有验证。
代码片段如下
var ngModelDirective = ['$rootScope', function($rootScope) {
return {
restrict: 'A',
require: ['ngModel', '^?form', '^?ngModelOptions'],
controller: NgModelController,
// Prelink needs to run before any input directive
// so that we can set the NgModelOptions in NgModelController
// before anyone else uses it.
priority: 1,
compile: function ngModelCompile(element) {
// Setup initial state of the control
element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
return {
pre: function ngModelPreLink(scope, element, attr, ctrls) {
var modelCtrl = ctrls[0],
formCtrl = ctrls[1] || modelCtrl.$$parentForm;
modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
// notify others, especially parent forms
formCtrl.$addControl(modelCtrl);
attr.$observe('name', function(newValue) {
if (modelCtrl.$name !== newValue) {
modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue);
}
});
scope.$on('$destroy', function() {
modelCtrl.$$parentForm.$removeControl(modelCtrl);
});
},
.....
form.$addControl = function(control) {
// Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
// and not added to the scope. Now we throw an error.
assertNotHasOwnProperty(control.$name, 'input');
controls.push(control);
if (control.$name) {
form[control.$name] = control;
}
control.$$parentForm = form;
};
....
form.$commitViewValue = function() {
forEach(controls, function(control) {
control.$commitViewValue();
});
};
下面我们来写个小例子,设置买一组商品所需要的总预算,系统根据用户勾选的商品自动计算预算,当然用户也可以自己输入,但是输入值除了基本的数字验证外,还必须不低于已勾选商品价格总和,即需要动态的限制用户可输入的最小值。
首先,我们可以先写好一个NumRangeValidateDirective,用于验证输入值的范围。如下,它绑定了两个输入min, max, 同时require了ngModel。 ngModel的$parsers里的方法决定如何将Dom值,即用户输入的值,转换到ng-model所绑定的值,而$formatters里的方法决定如何将model值呈现到界面上 。
因为我们要验证用户输入,所以往ngModel的$parsers里添加一个自定义的方法。
首先通过正则验证一下输入格式,比如’009’, ‘rrr’,不允许这样的输入。
然后与scope上的min, max分别比较一下,如果不符合范围,则通过 ngModel.$setValidity('numRange', ...); 设置一个numRange类别的invalid信息。
function NumRangeValidateDirective() {
return {
require: 'ngModel',
scope: {
min: '=',
max: '='
},
link: (scope, elem, attr, ngModel) => {
//For DOM -> model validation
ngModel.$parsers.push((value) => {
let valid = ((/^-?(0|([1-9]/d*))$/).test(`${value}`));
const res = _.toNumber(value);
valid = _.isUndefined(scope.min) ? valid : (valid && (res >= scope.min));
valid = _.isUndefined(scope.max) ? valid : (valid && (res <= scope.max));
ngModel.$setValidity('numRange', valid);
return valid ? res : undefined; // eslint-disable-line no-undefined
});
//For model -> DOM validation
// ngModel.$formatters.unshift((value) => {
// // ngModel.$setValidity('budget', blacklist.indexOf(value) === -1);
// return value;
// });
}
};
}
export default NumRangeValidateDirective;
往input上加上num-range-validate控件(angular自带一些验证,如ng-required,可以去 官网 查看)
form(name="ctrl.form")
.input-wrapper
input(name="budget", ng-model="ctrl.totalBudget", num-range-validate, min="ctrl.sum")
i.fa.fa-exclamation.error(ng-show="ctrl.form.budget.$invalid", title="您的预算不得低于已选物品总金额{{ctrl.sum}}")
可以通过 ctrl.form.budget.$error 拿到具体的错误信息。
附上 codepen Example 地址
参考