Skip to content

Commit 2271b72

Browse files
committed
fix(input[range]): correctly initialize with interpolated min / max values
The interpolation directive only sets the actual element attribute value after a digest passed. That means previously the min/max values on input range were not set when the first $render happened, so the browser would not adjust the input value according to min/max. This meant the range input and model would not be initialzed as expected. With this change, input range will set the actual element attribute value during its own linking phase, as it is already available on the attrs argument passed to the link fn. Fixes angular#14982
1 parent 21aa003 commit 2271b72

File tree

2 files changed

+75
-51
lines changed

2 files changed

+75
-51
lines changed

src/ng/directive/input.js

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1550,7 +1550,9 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15501550
var minVal = 0,
15511551
maxVal = 100,
15521552
supportsRange = ctrl.$$hasNativeValidators && element[0].type === 'range',
1553-
validity = element[0].validity;
1553+
validity = element[0].validity,
1554+
minAttrType = isDefined(attr.ngMin) ? 'ngMin' : isDefined(attr.min) ? 'min' : false,
1555+
maxAttrType = isDefined(attr.ngMax) ? 'ngMax' : isDefined(attr.max) ? 'max' : false;
15541556

15551557
var originalRender = ctrl.$render;
15561558

@@ -1563,11 +1565,54 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15631565
} :
15641566
originalRender;
15651567

1568+
if (minAttrType) {
1569+
ctrl.$validators.min = minAttrType === 'min' && supportsRange ?
1570+
function noopMinValidator() {
1571+
// Since all browsers set the input to a valid value, we don't need to check validity
1572+
return true;
1573+
} :
1574+
// ngMin doesn't set the min attr, so the browser doesn't adjust the input value as setting min would
1575+
function minValidator(modelValue, viewValue) {
1576+
return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal;
1577+
};
1578+
1579+
setInitialValueAndObserver(minAttrType, 'min', minChange);
1580+
}
1581+
1582+
if (maxAttrType) {
1583+
ctrl.$validators.max = maxAttrType === 'max' && supportsRange ?
1584+
function noopMaxValidator() {
1585+
// Since all browsers set the input to a valid value, we don't need to check validity
1586+
return true;
1587+
} :
1588+
// ngMax doesn't set the max attr, so the browser doesn't adjust the input value as setting max would
1589+
function maxValidator(modelValue, viewValue) {
1590+
return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal;
1591+
};
1592+
1593+
setInitialValueAndObserver(maxAttrType, 'max', maxChange);
1594+
}
1595+
1596+
function setInitialValueAndObserver(attrType, attrName, changeFn) {
1597+
var initialAttrValue;
1598+
if (attrType === attrName) {
1599+
initialAttrValue = attr[attrName];
1600+
// Set the actual element attribute so that the browser can adjust the value based on
1601+
// the max value, which might be interpolated
1602+
element.attr(attrName, initialAttrValue);
1603+
}
1604+
1605+
// This initalizes the min/max value, so that non-support browsers validate with the correct
1606+
// values during the initial $render
1607+
changeFn(initialAttrValue);
1608+
attr.$observe(attrName, changeFn);
1609+
}
1610+
15661611
function minChange(val) {
15671612
if (isDefined(val) && !isNumber(val)) {
15681613
val = parseFloat(val);
15691614
}
1570-
minVal = isNumber(val) && !isNaN(val) ? val : undefined;
1615+
minVal = isNumber(val) && !isNaN(val) ? val : 0;
15711616
// ignore changes before model is initialized
15721617
if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
15731618
return;
@@ -1587,36 +1632,19 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15871632
}
15881633
}
15891634

1590-
var minAttrType = isDefined(attr.ngMin) ? 'ngMin' : isDefined(attr.min) ? 'min' : false;
1591-
if (minAttrType) {
1592-
ctrl.$validators.min = isDefined(attr.min) && supportsRange ?
1593-
function noopMinValidator(value) {
1594-
// Since all browsers set the input to a valid value, we don't need to check validity
1595-
return true;
1596-
} :
1597-
// ngMin doesn't set the min attr, so the browser doesn't adjust the input value as setting min would
1598-
function minValidator(modelValue, viewValue) {
1599-
return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal;
1600-
};
1601-
1602-
// Assign minVal when the directive is linked. This won't run the validators as the model isn't ready yet
1603-
minChange(attr.min);
1604-
attr.$observe('min', minChange);
1605-
}
1606-
16071635
function maxChange(val) {
16081636
if (isDefined(val) && !isNumber(val)) {
16091637
val = parseFloat(val);
16101638
}
1611-
maxVal = isNumber(val) && !isNaN(val) ? val : undefined;
1639+
maxVal = isNumber(val) && !isNaN(val) ? val : 100;
16121640
// ignore changes before model is initialized
16131641
if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
16141642
return;
16151643
}
16161644

16171645
if (supportsRange && maxAttrType === 'max') {
16181646
var elVal = element.val();
1619-
// IE11 doesn't set the el val correctly if the maxVal is less than the element value
1647+
// IE11 doesn't set the el val correctly if the maxVal is greater than the element value
16201648
if (maxVal < elVal) {
16211649
element.val(maxVal);
16221650
elVal = minVal;
@@ -1627,22 +1655,6 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
16271655
ctrl.$validate();
16281656
}
16291657
}
1630-
var maxAttrType = isDefined(attr.max) ? 'max' : attr.ngMax ? 'ngMax' : false;
1631-
if (maxAttrType) {
1632-
ctrl.$validators.max = isDefined(attr.max) && supportsRange ?
1633-
function noopMaxValidator() {
1634-
// Since all browsers set the input to a valid value, we don't need to check validity
1635-
return true;
1636-
} :
1637-
// ngMax doesn't set the max attr, so the browser doesn't adjust the input value as setting max would
1638-
function maxValidator(modelValue, viewValue) {
1639-
return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal;
1640-
};
1641-
1642-
// Assign maxVal when the directive is linked. This won't run the validators as the model isn't ready yet
1643-
maxChange(attr.max);
1644-
attr.$observe('max', maxChange);
1645-
}
16461658

16471659
}
16481660

test/ng/directive/inputSpec.js

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2877,7 +2877,7 @@ describe('input', function() {
28772877
expect(inputElm.val()).toEqual('50');
28782878
});
28792879

2880-
it('should set model to 50 when no value specified', function() {
2880+
it('should set model to 50 when no value specified and default min/max', function() {
28812881
var inputElm = helper.compileInput('<input type="range" ng-model="age" />');
28822882

28832883
expect(inputElm.val()).toBe('50');
@@ -2887,7 +2887,7 @@ describe('input', function() {
28872887
expect(scope.age).toBe(50);
28882888
});
28892889

2890-
it('should parse non-number values to 50', function() {
2890+
it('should parse non-number values to 50 when default min/max', function() {
28912891
var inputElm = helper.compileInput('<input type="range" ng-model="age" />');
28922892

28932893
scope.$apply('age = 10');
@@ -3051,7 +3051,7 @@ describe('input', function() {
30513051
describe('ngMin', function() {
30523052

30533053
it('should validate', function() {
3054-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" ng-min="50" />');
3054+
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" ng-min="20" />');
30553055

30563056
helper.changeInputValueTo('1');
30573057
expect(inputElm).toBeInvalid();
@@ -3074,6 +3074,7 @@ describe('input', function() {
30743074
scope.min = 20;
30753075
scope.$digest();
30763076
expect(inputElm).toBeInvalid();
3077+
expect(inputElm.val()).toBe('15');
30773078

30783079
scope.min = null;
30793080
scope.$digest();
@@ -3093,6 +3094,18 @@ describe('input', function() {
30933094
describe('max', function() {
30943095

30953096
if (supportsRange) {
3097+
// Browsers that implement range will never allow you to set the value > max value
3098+
it('should initialize correctly with non-default model and max value', function() {
3099+
scope.value = 150;
3100+
scope.max = 150;
3101+
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" max="{{max}}" />');
3102+
3103+
expect(inputElm).toBeValid();
3104+
expect(inputElm.val()).toBe('150');
3105+
expect(scope.value).toBe(150);
3106+
expect(scope.form.alias.$error.max).toBeFalsy();
3107+
});
3108+
30963109
// Browsers that implement range will never allow you to set the value > max value
30973110
it('should validate', function() {
30983111
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" max="10" />');
@@ -3204,15 +3217,15 @@ describe('input', function() {
32043217
it('should validate', function() {
32053218
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" ng-max="5" />');
32063219

3207-
helper.changeInputValueTo('20');
3208-
expect(inputElm).toBeInvalid();
3209-
expect(scope.value).toBeUndefined();
3210-
expect(scope.form.alias.$error.max).toBeTruthy();
3211-
32123220
helper.changeInputValueTo('0');
32133221
expect(inputElm).toBeValid();
32143222
expect(scope.value).toBe(0);
32153223
expect(scope.form.alias.$error.max).toBeFalsy();
3224+
3225+
helper.changeInputValueTo('20');
3226+
expect(inputElm).toBeInvalid();
3227+
expect(scope.value).toBeUndefined();
3228+
expect(scope.form.alias.$error.max).toBeTruthy();
32163229
});
32173230

32183231
it('should validate even if the ngMax value changes on-the-fly', function() {
@@ -3245,22 +3258,21 @@ describe('input', function() {
32453258

32463259
describe('min and max', function() {
32473260

3248-
it('should keep the initial default value when min and max are specified', function() {
3261+
it('should set the correct initial value when min and max are specified', function() {
32493262
scope.max = 80;
32503263
scope.min = 40;
32513264
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" max="{{max}}" min="{{min}}" />');
32523265

3253-
expect(inputElm.val()).toBe('50');
3254-
expect(scope.value).toBe(50);
3266+
expect(inputElm.val()).toBe('60');
3267+
expect(scope.value).toBe(60);
32553268
});
32563269

3257-
32583270
it('should set element and model value to min if max is less than min', function() {
32593271
scope.min = 40;
32603272
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" max="{{max}}" min="{{min}}" />');
32613273

3262-
expect(inputElm.val()).toBe('50');
3263-
expect(scope.value).toBe(50);
3274+
expect(inputElm.val()).toBe('70');
3275+
expect(scope.value).toBe(70);
32643276

32653277
scope.max = 20;
32663278
scope.$digest();

0 commit comments

Comments
 (0)