我是最佳实践的坚定拥护者,特别是在角度方面,但我无法设法使用全新的$validators
管道功能。
案例很简单:使用$parser
,$formatter
和一些$validators
的指令增强了1个输入:
<input name="number" type="text" ng-model="number" number>
这是(简化)指令:
myApp.directive('number', [function() {
return {
restrict: 'A',
require: 'ngModel',
/*
* Must have higher priority than ngModel directive to make
* number (post)link function run after ngModel's one.
* ngModel's priority is 1.
*/
priority: 2,
link: function($scope, $element, $attrs, $controller) {
$controller.$parsers.push(function (value) {
return isFinite(value)? parseInt(value): undefined;
});
$controller.$formatters.push(function (value) {
return value.toString() || '';
});
$controller.$validators.minNumber = function(value) {
return value && value >= 1;
};
$controller.$validators.maxNumber = function(value) {
return value && value <= 10;
};
}
};
}]);
I made a little plunk to play with :)
我想要实现的行为是:考虑到存储在作用域中的初始值是有效的,如果用户输入无效,则防止它被破坏。保留旧版本,直到设置新的有效版本。
注意:在角度1.3之前,我可以直接在ngModelController
中使用$parser/$formatter
API执行此操作。我仍然可以用1.3做到这一点,但这不会是“角度方式”。
NB2:在我的应用中,我并不是真的使用数字,而是quantities。问题仍然存在。
答案 0 :(得分:6)
看起来您希望在验证后进行一些解析,将模型设置为最后一个有效值,而不是从视图派生的值。但是,我认为1.3管道的工作方式相反:解析在验证之前发生。
所以我的答案是按照你在1.2中的方式做到这一点:使用$parsers
设置验证密钥并将用户的输入转换回最新的有效值。
以下指令执行此操作,在指令中指定的validators
数组按顺序运行。如果以前的任何验证器都失败了,那么后面的验证器就不会运行:它假设一次可能发生一次验证错误。
与您的问题最相关的是,它维护模型中的最后一个有效值,并且只有在没有发生验证错误时才会覆盖。
myApp.directive('number', [function() {
return {
restrict: 'A',
require: 'ngModel',
/*
* Must have higher priority than ngModel directive to make
* number (post)link function run after ngModel's one.
* ngModel's priority is 1.
*/
priority: 2,
link: function($scope, $element, $attrs, $controller) {
var lastValid;
$controller.$parsers.push(function(value) {
value = parseInt(value);
lastValid = $controller.$modelValue;
var skip = false;
validators.forEach(function(validator) {
var isValid = skip || validator.validatorFn(value);
$controller.$setValidity(validator.key, isValid);
skip = skip || !isValid;
});
if ($controller.$valid) {
lastValid = value;
}
return lastValid;
});
$controller.$formatters.push(function(value) {
return value.toString() || '';
});
var validators = [{
key: 'isNumber',
validatorFn: function(value) {
return isFinite(value);
}
}, {
key: 'minNumber',
validatorFn: function(value) {
return value >= 1;
}
}, {
key: 'maxNumber',
validatorFn: function(value) {
return value <= 10;
}
}];
}
};
}]);
答案 1 :(得分:3)
我认为你从Angular-way而不是Angular-way方面过度思考。 1.3之前使用$parsers
管道是Angular-way,现在不是吗?
嗯,Angular-way也是ng-model
将模型设置为undefined
(默认情况下)为无效值。遵循Angular-way方向并定义另一个变量来存储“lastValid”值:
<input ng-model="foo" ng-maxlength="3"
ng-change="lastValidFoo = foo !== undefined ? foo : lastValidFoo"
ng-init="foo = lastValidFoo">
不需要特殊的指令,并且它不会试图绕过Angular本身正在做的事情 - 即Angular-way。 :)
答案 2 :(得分:2)
从Angular 1.3开始,您可以使用ngModelOptions指令更好地控制模型值何时更新。看看这个更新的Plunker,向您展示如何实现您正在寻找的功能:http://plnkr.co/edit/DoWbvlFMEtqF9gvJCjPF?p=preview
基本上,您将模型定义为getterSetter,并且仅在新值有效时才返回该值:
$scope.validNumber = function(value) {
return angular.isDefined(value) ? ($scope.number = value) : $scope.number;
}
$scope.modelOptions = {
getterSetter: true,
allowInvalid: false
};
然后使用此代码更新您的内容如下:
<input name="number" type="text" ng-model="validNumber" ng-model-options="modelOptions" number>
我真的希望这能回答你所有的问题,如果我能再帮忙,请告诉我。
莱昂。
答案 3 :(得分:1)
以下是我的plnkr及相关代码:
$controller.$$runValidators = function(originalRun) {
var lastModelValue, lastViewValue;
return function() {
var ctrl = this;
var doneCallback = arguments[arguments.length-1];
arguments[arguments.length-1] = function(allValid) {
doneCallback(allValid);
console.log(allValid);
console.log('valid:' +allValid+ ' value:' +ctrl.$viewValue);
if (ctrl.$viewValue) {
lastViewValue= allValid ? ctrl.$viewValue : lastViewValue | '';
lastModelValue= allValid ? ctrl.$modelValue : lastModelValue;
ctrl.$modelValue = allValid ? ctrl.$modelValue : lastModelValue;
ctrl.$$writeModelToScope();
ctrl.$viewValue = ctrl.$$lastCommittedViewValue = lastViewValue;
ctrl.$render();
}
console.log(ctrl.$viewValue + ' '+lastViewValue);
// console.log( ctrl.$modelValue);
};
originalRun.apply(this, arguments);
}
}($controller.$$runValidators);
它可以是有效的解决方案吗? 我认为你可以拦截角度验证流程的唯一方法是覆盖$$ runValidators。也许这段代码需要一些调整但是有效。