fix(input[number]): validate min/max against viewValue
This brings the validation in line with HTML5 validation, i.e. what the user has entered is validated, and not a possibly transformed value. Fixes #12761 Closes #16325 BREAKING CHANGE `input[type=number]` with `ngModel` now validates the input for the `max`/`min` restriction against the `ngModelController.$viewValue` instead of against the `ngModelController.$modelValue`. This affects apps that use `$parsers` or `$formatters` to transform the input / model value. If you rely on the $modelValue validation, you can overwrite the `min`/`max` validator from a custom directive, as seen in the following example directive definition object: ``` { restrict: 'A', require: 'ngModel', link: function(scope, element, attrs, ctrl) { var maxValidator = ctrl.$validators.max; ctrk.$validators.max = function(modelValue, viewValue) { return maxValidator(modelValue, modelValue); }; } } ```
This commit is contained in:
@@ -1604,8 +1604,8 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
var maxVal;
|
||||
|
||||
if (isDefined(attr.min) || attr.ngMin) {
|
||||
ctrl.$validators.min = function(value) {
|
||||
return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal;
|
||||
ctrl.$validators.min = function(modelValue, viewValue) {
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal;
|
||||
};
|
||||
|
||||
attr.$observe('min', function(val) {
|
||||
@@ -1616,8 +1616,8 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
|
||||
if (isDefined(attr.max) || attr.ngMax) {
|
||||
ctrl.$validators.max = function(value) {
|
||||
return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal;
|
||||
ctrl.$validators.max = function(modelValue, viewValue) {
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal;
|
||||
};
|
||||
|
||||
attr.$observe('max', function(val) {
|
||||
|
||||
@@ -2284,6 +2284,15 @@ describe('input', function() {
|
||||
|
||||
describe('number', function() {
|
||||
|
||||
// Helpers for min / max tests
|
||||
var subtract = function(value) {
|
||||
return value - 5;
|
||||
};
|
||||
|
||||
var add = function(value) {
|
||||
return value + 5;
|
||||
};
|
||||
|
||||
it('should reset the model if view is invalid', function() {
|
||||
var inputElm = helper.compileInput('<input type="number" ng-model="age"/>');
|
||||
|
||||
@@ -2465,6 +2474,29 @@ describe('input', function() {
|
||||
expect($rootScope.form.alias.$error.min).toBeFalsy();
|
||||
});
|
||||
|
||||
|
||||
it('should validate against the viewValue', function() {
|
||||
var inputElm = helper.compileInput(
|
||||
'<input type="number" ng-model-options="{allowInvalid: true}" ng-model="value" name="alias" min="10" />');
|
||||
|
||||
var ngModelCtrl = inputElm.controller('ngModel');
|
||||
ngModelCtrl.$parsers.push(subtract);
|
||||
|
||||
helper.changeInputValueTo('10');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.value).toBe(5);
|
||||
expect($rootScope.form.alias.$error.min).toBeFalsy();
|
||||
|
||||
ngModelCtrl.$parsers.pop();
|
||||
ngModelCtrl.$parsers.push(add);
|
||||
|
||||
helper.changeInputValueTo('5');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.alias.$error.min).toBeTruthy();
|
||||
expect($rootScope.value).toBe(10);
|
||||
});
|
||||
|
||||
|
||||
it('should validate even if min value changes on-the-fly', function() {
|
||||
$rootScope.min = undefined;
|
||||
var inputElm = helper.compileInput('<input type="number" ng-model="value" name="alias" min="{{min}}" />');
|
||||
@@ -2511,6 +2543,28 @@ describe('input', function() {
|
||||
expect($rootScope.form.alias.$error.min).toBeFalsy();
|
||||
});
|
||||
|
||||
|
||||
it('should validate against the viewValue', function() {
|
||||
var inputElm = helper.compileInput(
|
||||
'<input type="number" ng-model-options="{allowInvalid: true}" ng-model="value" name="alias" ng-min="10" />');
|
||||
var ngModelCtrl = inputElm.controller('ngModel');
|
||||
ngModelCtrl.$parsers.push(subtract);
|
||||
|
||||
helper.changeInputValueTo('10');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.value).toBe(5);
|
||||
expect($rootScope.form.alias.$error.min).toBeFalsy();
|
||||
|
||||
ngModelCtrl.$parsers.pop();
|
||||
ngModelCtrl.$parsers.push(add);
|
||||
|
||||
helper.changeInputValueTo('5');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.alias.$error.min).toBeTruthy();
|
||||
expect($rootScope.value).toBe(10);
|
||||
});
|
||||
|
||||
|
||||
it('should validate even if the ngMin value changes on-the-fly', function() {
|
||||
$rootScope.min = undefined;
|
||||
var inputElm = helper.compileInput('<input type="number" ng-model="value" name="alias" ng-min="min" />');
|
||||
@@ -2558,6 +2612,28 @@ describe('input', function() {
|
||||
expect($rootScope.form.alias.$error.max).toBeFalsy();
|
||||
});
|
||||
|
||||
|
||||
it('should validate against the viewValue', function() {
|
||||
var inputElm = helper.compileInput('<input type="number"' +
|
||||
'ng-model-options="{allowInvalid: true}" ng-model="value" name="alias" max="10" />');
|
||||
var ngModelCtrl = inputElm.controller('ngModel');
|
||||
ngModelCtrl.$parsers.push(add);
|
||||
|
||||
helper.changeInputValueTo('10');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.value).toBe(15);
|
||||
expect($rootScope.form.alias.$error.max).toBeFalsy();
|
||||
|
||||
ngModelCtrl.$parsers.pop();
|
||||
ngModelCtrl.$parsers.push(subtract);
|
||||
|
||||
helper.changeInputValueTo('15');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.alias.$error.max).toBeTruthy();
|
||||
expect($rootScope.value).toBe(10);
|
||||
});
|
||||
|
||||
|
||||
it('should validate even if max value changes on-the-fly', function() {
|
||||
$rootScope.max = undefined;
|
||||
var inputElm = helper.compileInput('<input type="number" ng-model="value" name="alias" max="{{max}}" />');
|
||||
@@ -2604,6 +2680,28 @@ describe('input', function() {
|
||||
expect($rootScope.form.alias.$error.max).toBeFalsy();
|
||||
});
|
||||
|
||||
|
||||
it('should validate against the viewValue', function() {
|
||||
var inputElm = helper.compileInput('<input type="number"' +
|
||||
'ng-model-options="{allowInvalid: true}" ng-model="value" name="alias" ng-max="10" />');
|
||||
var ngModelCtrl = inputElm.controller('ngModel');
|
||||
ngModelCtrl.$parsers.push(add);
|
||||
|
||||
helper.changeInputValueTo('10');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.value).toBe(15);
|
||||
expect($rootScope.form.alias.$error.max).toBeFalsy();
|
||||
|
||||
ngModelCtrl.$parsers.pop();
|
||||
ngModelCtrl.$parsers.push(subtract);
|
||||
|
||||
helper.changeInputValueTo('15');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.alias.$error.max).toBeTruthy();
|
||||
expect($rootScope.value).toBe(10);
|
||||
});
|
||||
|
||||
|
||||
it('should validate even if the ngMax value changes on-the-fly', function() {
|
||||
$rootScope.max = undefined;
|
||||
var inputElm = helper.compileInput('<input type="number" ng-model="value" name="alias" ng-max="max" />');
|
||||
|
||||
Reference in New Issue
Block a user