@@ -1036,22 +1036,49 @@ var inputType = {
1036
1036
* @name input[range]
1037
1037
*
1038
1038
* @description
1039
- * Native range input with validation and transformation. Sets the `number` validation
1040
- * to always have a valid number. Note that IE9 does not support the `range` type, but falls back
1039
+ * Native range input with validation and transformation.
1040
+ *
1041
+ * The model for the range input must always be a `Number`.
1042
+ *
1043
+ * IE9 does not support the `range` type, but falls back
1041
1044
* to a text input. Model binding, validation and number parsing are nevertheless supported.
1042
1045
*
1043
- * @param {string } ngModel Assignable angular expression to data-bind to.
1046
+ * Browsers that support range (latest Chrome, Safari, Firefox, Edge) treat `input[range]`
1047
+ * in a way that never allows the input to hold an invalid value. That means:
1048
+ * - any non-numerical value is set to `(max + min) / 2`.
1049
+ * - any numerical value that is less than the current min val, or greater than the current max val
1050
+ * is set to the min / max val respectively.
1051
+ *
1052
+ * This has the following consequences for Angular:
1053
+ *
1054
+ * Since the element value should always reflect the current model value, a range input
1055
+ * will set the bound ngModel expression to the value that the browser has set for the
1056
+ * input element. For example, in the following input `<input type="range" ng-model="model.value">`,
1057
+ * if I set `model.value = null`, the browser will set the input to `'50'`. Angular will then set
1058
+ * the model to `50`, to prevent input and model value being out of sync.
1059
+ *
1060
+ * This does not only affect changes to the model value, but also to the values of the `min` and
1061
+ * `max` attributes. When these change in a way that will cause the browser to modify the input value,
1062
+ * Angular will also update the model value.
1063
+ *
1064
+ * Automatic value adjustment also means that a range input element can never have the `required`,
1065
+ * `min`, or `max` errors, except when using `ngMax` and `ngMin`, which are not affected by automatic
1066
+ * value adjustment, because they do not set the `min` and `max` attributes.
1067
+ *
1068
+ * @param {string } ngModel Assignable angular expression to data-bind to.
1044
1069
* @param {string= } name Property name of the form under which the control is published.
1045
1070
* @param {string= } min Sets the `min` validation to ensure that the value entered is greater
1046
1071
* than `min`. Can be interpolated.
1047
1072
* @param {string= } max Sets the `max` validation to ensure that the value entered is less than `max`.
1048
1073
* Can be interpolated.
1049
1074
* @param {string= } ngMin Takes an expression. Sets the `min` validation to ensure that the value
1050
1075
* entered is greater than `min`. Does not set the `min` attribute and therefore
1051
- * adds no native HTML5 validation.
1076
+ * adds no native HTML5 validation. It also means the browser won't adjust the
1077
+ * element value in case `min` is greater than the current value.
1052
1078
* @param {string= } ngMax Takes an expression. Sets the `max` validation to ensure that the value
1053
1079
* entered is less than `max`. Does not set the `min` attribute and therefore
1054
- * adds no native HTML5 validation.
1080
+ * adds no native HTML5 validation. It also means the browser won't adjust the
1081
+ * element value in case `max` is less than the current value.
1055
1082
* @param {string= } ngChange Angular expression to be executed when the ngModel value changes due
1056
1083
* to user interaction with the input element.
1057
1084
*
@@ -1062,18 +1089,52 @@ var inputType = {
1062
1089
angular.module('rangeExample', [])
1063
1090
.controller('ExampleController', ['$scope', function($scope) {
1064
1091
$scope.value = 75;
1092
+ $scope.min = 10;
1093
+ $scope.max = 90;
1065
1094
}]);
1066
1095
</script>
1067
1096
<form name="myForm" ng-controller="ExampleController">
1068
- Number: <input type="range" name="input" ng-model="value"
1069
- min="10" max="90">
1070
- <tt>value = {{value}}</tt><br/>
1071
- <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
1072
- <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
1073
- <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
1097
+
1098
+ Model as range: <input type="range" name="range" ng-model="value"
1099
+ min="{{min}}" max="{{max}}">
1100
+ <hr>
1101
+ Model as number: <input type="number" ng-model="value"><br>
1102
+ Min: <input type="number" ng-model="min"><br>
1103
+ Max: <input type="number" ng-model="min"><br>
1104
+ value = <code>{{value}}</code><br/>
1105
+ myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/>
1106
+ myForm.range.$error = <code>{{myForm.range.$error}}</code>
1107
+ </form>
1108
+ </file>
1109
+ </example>
1110
+
1111
+ * ## Range Input with ngMin & ngMax attributes
1112
+
1113
+ * @example
1114
+ <example name="range-input-directive-ng" module="rangeExample">
1115
+ <file name="index.html">
1116
+ <script>
1117
+ angular.module('rangeExample', [])
1118
+ .controller('ExampleController', ['$scope', function($scope) {
1119
+ $scope.value = 75;
1120
+ $scope.min = 10;
1121
+ $scope.max = 90;
1122
+ }]);
1123
+ </script>
1124
+ <form name="myForm" ng-controller="ExampleController">
1125
+ Model as range: <input type="range" name="range" ng-model="value"
1126
+ ng-min="10" ng-max="90">
1127
+ <hr>
1128
+ Model as number: <input type="number" ng-model="value"><br>
1129
+ Min: <input type="number" ng-model="min"><br>
1130
+ Max: <input type="number" ng-model="min"><br>
1131
+ value = <code>{{value}}</code><br/>
1132
+ myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/>
1133
+ myForm.range.$error = <code>{{myForm.range.$error}}</code>
1074
1134
</form>
1075
1135
</file>
1076
1136
</example>
1137
+
1077
1138
*/
1078
1139
'range' : rangeInputType ,
1079
1140
@@ -1528,32 +1589,32 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
1528
1589
return ;
1529
1590
}
1530
1591
1531
- // TODO(matsko): implement validateLater to reduce number of validations
1532
1592
if ( supportsRange && minAttrType === 'min' ) {
1533
1593
var elVal = element . val ( ) ;
1534
- // IE11 doesn't set the el val correctly if the maxVal is less than the element value
1594
+ // IE11 doesn't set the el val correctly if the minVal is greater than the element value
1535
1595
if ( minVal > elVal ) {
1536
1596
element . val ( minVal ) ;
1537
1597
}
1538
1598
ctrl . $setViewValue ( element . val ( ) ) ;
1539
1599
} else {
1600
+ // TODO(matsko): implement validateLater to reduce number of validations
1540
1601
ctrl . $validate ( ) ;
1541
1602
}
1542
1603
}
1543
1604
1544
1605
var minAttrType = isDefined ( attr . min ) ? 'min' : attr . ngMin ? 'ngMin' : false ;
1545
1606
if ( minAttrType ) {
1546
- // Since ngMin doesn't set the min attr, the browser doesn't adjust the input value as setting min would
1547
1607
ctrl . $validators . min = isDefined ( attr . min ) && supportsRange ?
1548
1608
function noopMinValidator ( value ) {
1549
1609
// Since all browsers set the input to a valid value, we don't need to check validity
1550
1610
return true ;
1551
1611
} :
1612
+ // ngMin doesn't set the min attr, so the browser doesn't adjust the input value as setting min would
1552
1613
function minValidator ( modelValue , viewValue ) {
1553
1614
return ctrl . $isEmpty ( viewValue ) || isUndefined ( minVal ) || viewValue >= minVal ;
1554
1615
} ;
1555
1616
1556
- // Assign minVal when the directive is linked. This won't $validate as the model isn't ready yet
1617
+ // Assign minVal when the directive is linked. This won't run the validators as the model isn't ready yet
1557
1618
minChange ( attr . min ) ;
1558
1619
attr . $observe ( 'min' , minChange ) ;
1559
1620
}
@@ -1563,7 +1624,6 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
1563
1624
val = parseFloat ( val ) ;
1564
1625
}
1565
1626
maxVal = isNumber ( val ) && ! isNaN ( val ) ? val : undefined ;
1566
- // TODO(matsko): implement validateLater to reduce number of validations
1567
1627
// ignore changes before model is initialized
1568
1628
if ( isNumber ( ctrl . $modelValue ) && isNaN ( ctrl . $modelValue ) ) {
1569
1629
return ;
@@ -1577,22 +1637,23 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
1577
1637
}
1578
1638
ctrl . $setViewValue ( element . val ( ) ) ;
1579
1639
} else {
1640
+ // TODO(matsko): implement validateLater to reduce number of validations
1580
1641
ctrl . $validate ( ) ;
1581
1642
}
1582
1643
}
1583
1644
var maxAttrType = isDefined ( attr . max ) ? 'max' : attr . ngMax ? 'ngMax' : false ;
1584
1645
if ( maxAttrType ) {
1585
- // Since ngMax doesn't set the max attr, the browser doesn't adjust the input value as setting max would
1586
1646
ctrl . $validators . max = isDefined ( attr . max ) && supportsRange ?
1587
1647
function noopMaxValidator ( ) {
1588
1648
// Since all browsers set the input to a valid value, we don't need to check validity
1589
1649
return true ;
1590
1650
} :
1651
+ // ngMax doesn't set the max attr, so the browser doesn't adjust the input value as setting max would
1591
1652
function maxValidator ( modelValue , viewValue ) {
1592
1653
return ctrl . $isEmpty ( viewValue ) || isUndefined ( maxVal ) || viewValue <= maxVal ;
1593
1654
} ;
1594
1655
1595
- // Assign maxVal when the directive is linked. This won't $validate as the model isn't ready yet
1656
+ // Assign maxVal when the directive is linked. This won't run the validators as the model isn't ready yet
1596
1657
maxChange ( attr . max ) ;
1597
1658
attr . $observe ( 'max' , maxChange ) ;
1598
1659
}
0 commit comments