Skip to content

Commit 9f50e02

Browse files
fix(input[range]): correctly handle min/max; remove ngMin/ngMax support
This commit fixes the handling of min/max, and removes support for ngMin/ngMax: min/max: Previously, interpolated min/max values on input range were not set when the first $render happens, because the interpolation directive only sets the actual element attribute value after a digest passes. That means that the browser would not adjust the input value according to min/max and 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. ngMin/ngMax Since ng prefixed attributes do not set their corresponding element attribute, the range input would always have min = 0, and max = 100 (in supported browsers), regardless of the value in ngMin/ngMax. This is confusing and not very useful, so it's better to not support these attributes at all. The commit also fixes a test which used an interpolation inside an attribute that expects an expression. Fixes angular#14982 PR (angular#14996) (reverted from commit b78539b)
1 parent c29b799 commit 9f50e02

File tree

3 files changed

+149
-165
lines changed

3 files changed

+149
-165
lines changed

src/ng/directive/input.js

+51-49
Original file line numberDiff line numberDiff line change
@@ -1061,18 +1061,23 @@ var inputType = {
10611061
* Angular will also update the model value.
10621062
*
10631063
* Automatic value adjustment also means that a range input element can never have the `required`,
1064-
* `min`, or `max` errors.
1065-
*
1066-
* Note that `input[range]` is not compatible with`ngMax` and `ngMin`, because they do not set the
1067-
* `min` and `max` attributes, which means that the browser won't automatically adjust the input
1068-
* value based on their values, and will always assume min = 0 and max = 100.
1064+
* `min`, or `max` errors, except when using `ngMax` and `ngMin`, which are not affected by automatic
1065+
* value adjustment, because they do not set the `min` and `max` attributes.
10691066
*
10701067
* @param {string} ngModel Assignable angular expression to data-bind to.
10711068
* @param {string=} name Property name of the form under which the control is published.
10721069
* @param {string=} min Sets the `min` validation to ensure that the value entered is greater
10731070
* than `min`. Can be interpolated.
10741071
* @param {string=} max Sets the `max` validation to ensure that the value entered is less than `max`.
10751072
* Can be interpolated.
1073+
* @param {string=} ngMin Takes an expression. Sets the `min` validation to ensure that the value
1074+
* entered is greater than `min`. Does not set the `min` attribute and therefore
1075+
* adds no native HTML5 validation. It also means the browser won't adjust the
1076+
* element value in case `min` is greater than the current value.
1077+
* @param {string=} ngMax Takes an expression. Sets the `max` validation to ensure that the value
1078+
* entered is less than `max`. Does not set the `max` attribute and therefore
1079+
* adds no native HTML5 validation. It also means the browser won't adjust the
1080+
* element value in case `max` is less than the current value.
10761081
* @param {string=} ngChange Angular expression to be executed when the ngModel value changes due
10771082
* to user interaction with the input element.
10781083
*
@@ -1542,12 +1547,10 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15421547
numberFormatterParser(ctrl);
15431548
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
15441549

1545-
var supportsRange = ctrl.$$hasNativeValidators && element[0].type === 'range',
1546-
minVal = supportsRange ? 0 : undefined,
1547-
maxVal = supportsRange ? 100 : undefined,
1548-
validity = element[0].validity,
1549-
hasMinAttr = isDefined(attr.min),
1550-
hasMaxAttr = isDefined(attr.max);
1550+
var minVal = 0,
1551+
maxVal = 100,
1552+
supportsRange = ctrl.$$hasNativeValidators && element[0].type === 'range',
1553+
validity = element[0].validity;
15511554

15521555
var originalRender = ctrl.$render;
15531556

@@ -1560,39 +1563,6 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15601563
} :
15611564
originalRender;
15621565

1563-
if (hasMinAttr) {
1564-
ctrl.$validators.min = supportsRange ?
1565-
// Since all browsers set the input to a valid value, we don't need to check validity
1566-
function noopMinValidator() { return true; } :
1567-
// non-support browsers validate the range
1568-
function minValidator(modelValue, viewValue) {
1569-
return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal;
1570-
};
1571-
1572-
setInitialValueAndObserver('min', minChange);
1573-
}
1574-
1575-
if (hasMaxAttr) {
1576-
ctrl.$validators.max = supportsRange ?
1577-
// Since all browsers set the input to a valid value, we don't need to check validity
1578-
function noopMaxValidator() { return true; } :
1579-
// ngMax doesn't set the max attr, so the browser doesn't adjust the input value as setting max would
1580-
function maxValidator(modelValue, viewValue) {
1581-
return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal;
1582-
};
1583-
1584-
setInitialValueAndObserver('max', maxChange);
1585-
}
1586-
1587-
function setInitialValueAndObserver(htmlAttrName, changeFn) {
1588-
// interpolated attributes set the attribute value only after a digest, but we need the
1589-
// attribute value when the input is first rendered, so that the browser can adjust the
1590-
// input value based on the min/max value
1591-
element.attr(htmlAttrName, attr[htmlAttrName]);
1592-
1593-
attr.$observe(htmlAttrName, changeFn);
1594-
}
1595-
15961566
function minChange(val) {
15971567
if (isDefined(val) && !isNumber(val)) {
15981568
val = parseFloat(val);
@@ -1603,12 +1573,12 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
16031573
return;
16041574
}
16051575

1606-
if (supportsRange) {
1576+
if (supportsRange && minAttrType === 'min') {
16071577
var elVal = element.val();
16081578
// IE11 doesn't set the el val correctly if the minVal is greater than the element value
16091579
if (minVal > elVal) {
1580+
element.val(minVal);
16101581
elVal = minVal;
1611-
element.val(elVal);
16121582
}
16131583
ctrl.$setViewValue(elVal);
16141584
} else {
@@ -1617,6 +1587,23 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
16171587
}
16181588
}
16191589

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+
16201607
function maxChange(val) {
16211608
if (isDefined(val) && !isNumber(val)) {
16221609
val = parseFloat(val);
@@ -1627,20 +1614,35 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
16271614
return;
16281615
}
16291616

1630-
if (supportsRange) {
1617+
if (supportsRange && maxAttrType === 'max') {
16311618
var elVal = element.val();
16321619
// IE11 doesn't set the el val correctly if the maxVal is less than the element value
16331620
if (maxVal < elVal) {
16341621
element.val(maxVal);
1635-
// IE11 and Chrome don't set the value to the minVal when max < min
1636-
elVal = maxVal < minVal ? minVal : maxVal;
1622+
elVal = minVal;
16371623
}
16381624
ctrl.$setViewValue(elVal);
16391625
} else {
16401626
// TODO(matsko): implement validateLater to reduce number of validations
16411627
ctrl.$validate();
16421628
}
16431629
}
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+
}
16441646

16451647
}
16461648

0 commit comments

Comments
 (0)