Skip to content

Commit ee6286a

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 ee6286a

File tree

2 files changed

+83
-47
lines changed

2 files changed

+83
-47
lines changed

src/ng/directive/input.js

+49-37
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

+34-10
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');
@@ -2949,8 +2949,20 @@ describe('input', function() {
29492949
describe('min', function() {
29502950

29512951
if (supportsRange) {
2952+
2953+
it('should initialize correctly with non-default model and min value', function() {
2954+
scope.value = -3;
2955+
scope.min = -5;
2956+
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" min="{{min}}" />');
2957+
2958+
expect(inputElm).toBeValid();
2959+
expect(inputElm.val()).toBe('-3');
2960+
expect(scope.value).toBe(-3);
2961+
expect(scope.form.alias.$error.min).toBeFalsy();
2962+
});
2963+
29522964
// Browsers that implement range will never allow you to set the value < min values
2953-
it('should validate', function() {
2965+
it('should adjust invalid input values', function() {
29542966
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" min="10" />');
29552967

29562968
helper.changeInputValueTo('5');
@@ -3051,7 +3063,7 @@ describe('input', function() {
30513063
describe('ngMin', function() {
30523064

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

30563068
helper.changeInputValueTo('1');
30573069
expect(inputElm).toBeInvalid();
@@ -3074,6 +3086,7 @@ describe('input', function() {
30743086
scope.min = 20;
30753087
scope.$digest();
30763088
expect(inputElm).toBeInvalid();
3089+
expect(inputElm.val()).toBe('15');
30773090

30783091
scope.min = null;
30793092
scope.$digest();
@@ -3093,6 +3106,18 @@ describe('input', function() {
30933106
describe('max', function() {
30943107

30953108
if (supportsRange) {
3109+
// Browsers that implement range will never allow you to set the value > max value
3110+
it('should initialize correctly with non-default model and max value', function() {
3111+
scope.value = 130;
3112+
scope.max = 150;
3113+
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" max="{{max}}" />');
3114+
3115+
expect(inputElm).toBeValid();
3116+
expect(inputElm.val()).toBe('130');
3117+
expect(scope.value).toBe(130);
3118+
expect(scope.form.alias.$error.max).toBeFalsy();
3119+
});
3120+
30963121
// Browsers that implement range will never allow you to set the value > max value
30973122
it('should validate', function() {
30983123
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" max="10" />');
@@ -3245,22 +3270,21 @@ describe('input', function() {
32453270

32463271
describe('min and max', function() {
32473272

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

3253-
expect(inputElm.val()).toBe('50');
3254-
expect(scope.value).toBe(50);
3278+
expect(inputElm.val()).toBe('60');
3279+
expect(scope.value).toBe(60);
32553280
});
32563281

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

3262-
expect(inputElm.val()).toBe('50');
3263-
expect(scope.value).toBe(50);
3286+
expect(inputElm.val()).toBe('70');
3287+
expect(scope.value).toBe(70);
32643288

32653289
scope.max = 20;
32663290
scope.$digest();

0 commit comments

Comments
 (0)