Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 07b8761

Browse files
gkalpakpetebacondarwin
authored andcommitted
fix(input[range]): make support for input[range] opt-in
Closes #15229
1 parent 2e7121b commit 07b8761

File tree

3 files changed

+89
-35
lines changed

3 files changed

+89
-35
lines changed

docs/content/error/ngModel/numfmt.ngdoc

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
@fullName Model is not of type `number`
44
@description
55

6-
The `input[number]` and `input[range]` directives require the model to be a `number`.
6+
The `input[type="number"]` and `input[type="range"][ng-input-range]` directives require the model to
7+
be a `number`.
78

89
If the model is something else, this error will be thrown.
910

@@ -17,7 +18,7 @@ pipeline.
1718
## Example
1819

1920
In this example, our model stores the number as a string, so we provide the `stringToNumber`
20-
directive to convert it into the format the `input[number]` directive expects.
21+
directive to convert it into the format the `input[type="number"]` directive expects.
2122

2223

2324
<example module="numfmt-error-module" name="number-format-error">

src/ng/directive/input.js

+34-6
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,28 @@ var inputType = {
10341034
* @description
10351035
* Native range input with validation and transformation.
10361036
*
1037+
* <div class="alert alert-warning">
1038+
* <p>
1039+
* In v1.5.9+, in order to avoid interfering with already existing, custom directives for
1040+
* `input[range]`, you need to let Angular know that you want to enable its built-in support.
1041+
* You can do this by adding the `ng-input-range` attribute to the input element. E.g.:
1042+
* `<input type="range" ng-input-range ... />`
1043+
* </p><br />
1044+
* <p>
1045+
* Input elements without the `ng-input-range` attibute will continue to be treated the same
1046+
* as in previous versions (e.g. their model value will be a string not a number and Angular
1047+
* will not take `min`/`max`/`step` attributes and properties into account).
1048+
* </p><br />
1049+
* <p>
1050+
* **Note:** From v1.6.x onwards, the support for `input[range]` will be always enabled and
1051+
* the `ng-input-range` attribute will have no effect.
1052+
* </p><br />
1053+
* <p>
1054+
* This documentation page refers to elements which have the built-in support enabled; i.e.
1055+
* elements _with_ the `ng-input-range` attribute.
1056+
* </p>
1057+
* </div>
1058+
*
10371059
* The model for the range input must always be a `Number`.
10381060
*
10391061
* IE9 and other browsers that do not support the `range` type fall back
@@ -1055,7 +1077,7 @@ var inputType = {
10551077
*
10561078
* Since the element value should always reflect the current model value, a range input
10571079
* will set the bound ngModel expression to the value that the browser has set for the
1058-
* input element. For example, in the following input `<input type="range" ng-model="model.value">`,
1080+
* input element. For example, in the following input `<input type="range" ng-input-range ng-model="model.value">`,
10591081
* if the application sets `model.value = null`, the browser will set the input to `'50'`.
10601082
* Angular will then set the model to `50`, to prevent input and model value being out of sync.
10611083
*
@@ -1074,10 +1096,12 @@ var inputType = {
10741096
* instead may set the `stepMismatch` error. If that's the case, the Angular will set the `step`
10751097
* error on the input, and set the model to `undefined`.
10761098
*
1077-
* Note that `input[range]` is not compatible with`ngMax`, `ngMin`, and `ngStep`, because they do
1099+
* Note that `input[range]` is not compatible with `ngMax`, `ngMin`, and `ngStep`, because they do
10781100
* not set the `min` and `max` attributes, which means that the browser won't automatically adjust
10791101
* the input value based on their values, and will always assume min = 0, max = 100, and step = 1.
10801102
*
1103+
* @param ngInputRange The presense of this attribute enables the built-in support for
1104+
* `input[range]`.
10811105
* @param {string} ngModel Assignable angular expression to data-bind to.
10821106
* @param {string=} name Property name of the form under which the control is published.
10831107
* @param {string=} min Sets the `min` validation to ensure that the value entered is greater
@@ -1102,7 +1126,7 @@ var inputType = {
11021126
</script>
11031127
<form name="myForm" ng-controller="ExampleController">
11041128
1105-
Model as range: <input type="range" name="range" ng-model="value" min="{{min}}" max="{{max}}">
1129+
Model as range: <input type="range" ng-input-range name="range" ng-model="value" min="{{min}}" max="{{max}}">
11061130
<hr>
11071131
Model as number: <input type="number" ng-model="value"><br>
11081132
Min: <input type="number" ng-model="min"><br>
@@ -1128,7 +1152,7 @@ var inputType = {
11281152
}]);
11291153
</script>
11301154
<form name="myForm" ng-controller="ExampleController">
1131-
Model as range: <input type="range" name="range" ng-model="value" ng-min="min" ng-max="max">
1155+
Model as range: <input type="range" ng-input-range name="range" ng-model="value" ng-min="min" ng-max="max">
11321156
<hr>
11331157
Model as number: <input type="number" ng-model="value"><br>
11341158
Min: <input type="number" ng-model="min"><br>
@@ -1521,8 +1545,8 @@ function parseNumberAttrVal(val) {
15211545

15221546
function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15231547
badInputChecker(scope, element, attr, ctrl);
1524-
numberFormatterParser(ctrl);
15251548
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
1549+
numberFormatterParser(ctrl);
15261550

15271551
if (isDefined(attr.min) || attr.ngMin) {
15281552
var minVal;
@@ -1971,7 +1995,11 @@ var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
19711995
link: {
19721996
pre: function(scope, element, attr, ctrls) {
19731997
if (ctrls[0]) {
1974-
(inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
1998+
var type = lowercase(attr.type);
1999+
if ((type === 'range') && !attr.hasOwnProperty('ngInputRange')) {
2000+
type = 'text';
2001+
}
2002+
(inputType[type] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
19752003
$browser, $filter, $parse);
19762004
}
19772005
}

test/ng/directive/inputSpec.js

+52-27
Original file line numberDiff line numberDiff line change
@@ -2801,6 +2801,26 @@ describe('input', function() {
28012801
scope = $rootScope;
28022802
});
28032803

2804+
it('should be treated as `input[text]` without the `ng-input-range` attribute', function() {
2805+
var inputElm = helper.compileInput('<input type="range" ng-model="age" />');
2806+
var ngModel = inputElm.controller('ngModel');
2807+
2808+
helper.changeInputValueTo(25);
2809+
2810+
expect(scope.age).toBe('25');
2811+
expect(ngModel.$$parserName).toBeUndefined();
2812+
});
2813+
2814+
it('should not be treated as `input[text]` with the `ng-input-range` attribute', function() {
2815+
var inputElm = helper.compileInput('<input type="range" ng-model="age" ng-input-range />');
2816+
var ngModel = inputElm.controller('ngModel');
2817+
2818+
helper.changeInputValueTo('25');
2819+
2820+
expect(scope.age).toBe(25);
2821+
expect(ngModel.$$parserName).toBe('number');
2822+
});
2823+
28042824
if (supportsRange) {
28052825
// This behavior only applies to browsers that implement the range input, which do not
28062826
// allow to set a non-number value and will set the value of the input to 50 even when you
@@ -2809,7 +2829,7 @@ describe('input', function() {
28092829
// sense if the input value is a string. These browsers will mark the input as invalid instead.
28102830

28112831
it('should render as 50 if null', function() {
2812-
var inputElm = helper.compileInput('<input type="range" ng-model="age" />');
2832+
var inputElm = compileRangeInput('ng-model="age"');
28132833

28142834
helper.changeInputValueTo('25');
28152835
expect(scope.age).toBe(25);
@@ -2820,7 +2840,7 @@ describe('input', function() {
28202840
});
28212841

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

28252845
expect(inputElm.val()).toBe('50');
28262846

@@ -2830,7 +2850,7 @@ describe('input', function() {
28302850
});
28312851

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

28352855
scope.$apply('age = 10');
28362856
expect(inputElm.val()).toBe('10');
@@ -2843,7 +2863,7 @@ describe('input', function() {
28432863
} else {
28442864

28452865
it('should reset the model if view is invalid', function() {
2846-
var inputElm = helper.compileInput('<input type="range" ng-model="age"/>');
2866+
var inputElm = compileRangeInput('ng-model="age"');
28472867

28482868
scope.$apply('age = 100');
28492869
expect(inputElm.val()).toBe('100');
@@ -2856,7 +2876,7 @@ describe('input', function() {
28562876
}
28572877

28582878
it('should parse the input value to a Number', function() {
2859-
var inputElm = helper.compileInput('<input type="range" ng-model="age" />');
2879+
var inputElm = compileRangeInput('ng-model="age"');
28602880

28612881
helper.changeInputValueTo('75');
28622882
expect(scope.age).toBe(75);
@@ -2866,7 +2886,7 @@ describe('input', function() {
28662886
it('should only invalidate the model if suffering from bad input when the data is parsed', function() {
28672887
scope.age = 60;
28682888

2869-
var inputElm = helper.compileInput('<input type="range" ng-model="age" />', {
2889+
var inputElm = compileRangeInput('ng-model="age"', {
28702890
valid: false,
28712891
badInput: true
28722892
});
@@ -2883,7 +2903,7 @@ describe('input', function() {
28832903
it('should throw if the model value is not a number', function() {
28842904
expect(function() {
28852905
scope.value = 'one';
2886-
var inputElm = helper.compileInput('<input type="range" ng-model="value" />');
2906+
var inputElm = compileRangeInput('ng-model="value"');
28872907
}).toThrowMinErr('ngModel', 'numfmt', 'Expected `one` to be a number');
28882908
});
28892909

@@ -2895,7 +2915,7 @@ describe('input', function() {
28952915
it('should initialize correctly with non-default model and min value', function() {
28962916
scope.value = -3;
28972917
scope.min = -5;
2898-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" min="{{min}}" />');
2918+
var inputElm = compileRangeInput('ng-model="value" name="alias" min="{{min}}"');
28992919

29002920
expect(inputElm).toBeValid();
29012921
expect(inputElm.val()).toBe('-3');
@@ -2905,7 +2925,7 @@ describe('input', function() {
29052925

29062926
// Browsers that implement range will never allow you to set the value < min values
29072927
it('should adjust invalid input values', function() {
2908-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" min="10" />');
2928+
var inputElm = compileRangeInput('ng-model="value" name="alias" min="10"');
29092929

29102930
helper.changeInputValueTo('5');
29112931
expect(inputElm).toBeValid();
@@ -2921,7 +2941,7 @@ describe('input', function() {
29212941
it('should set the model to the min val if it is less than the min val', function() {
29222942
scope.value = -10;
29232943
// Default min is 0
2924-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" min="{{min}}" />');
2944+
var inputElm = compileRangeInput('ng-model="value" name="alias" min="{{min}}"');
29252945

29262946
expect(inputElm).toBeValid();
29272947
expect(inputElm.val()).toBe('0');
@@ -2936,7 +2956,7 @@ describe('input', function() {
29362956

29372957
it('should adjust the element and model value when the min value changes on-the-fly', function() {
29382958
scope.min = 10;
2939-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" min="{{min}}" />');
2959+
var inputElm = compileRangeInput('ng-model="value" name="alias" min="{{min}}"');
29402960

29412961
helper.changeInputValueTo('15');
29422962
expect(inputElm).toBeValid();
@@ -2970,7 +2990,7 @@ describe('input', function() {
29702990
// input[type=range] will become type=text in browsers that don't support it
29712991

29722992
it('should validate if "range" is not implemented', function() {
2973-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" min="10" />');
2993+
var inputElm = compileRangeInput('ng-model="value" name="alias" min="10"');
29742994

29752995
helper.changeInputValueTo('5');
29762996
expect(inputElm).toBeInvalid();
@@ -2985,7 +3005,7 @@ describe('input', function() {
29853005

29863006
it('should not assume a min val of 0 if the min interpolates to a non-number', function() {
29873007
scope.value = -10;
2988-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" min="{{min}}" />');
3008+
var inputElm = compileRangeInput('ng-model="value" name="alias" min="{{min}}"');
29893009

29903010
expect(inputElm).toBeValid();
29913011
expect(inputElm.val()).toBe('-10');
@@ -3013,7 +3033,7 @@ describe('input', function() {
30133033

30143034
it('should validate even if the min value changes on-the-fly', function() {
30153035
scope.min = 10;
3016-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" min="{{min}}" />');
3036+
var inputElm = compileRangeInput('ng-model="value" name="alias" min="{{min}}"');
30173037

30183038
helper.changeInputValueTo('15');
30193039
expect(inputElm).toBeValid();
@@ -3054,7 +3074,7 @@ describe('input', function() {
30543074
it('should initialize correctly with non-default model and max value', function() {
30553075
scope.value = 130;
30563076
scope.max = 150;
3057-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" max="{{max}}" />');
3077+
var inputElm = compileRangeInput('ng-model="value" name="alias" max="{{max}}"');
30583078

30593079
expect(inputElm).toBeValid();
30603080
expect(inputElm.val()).toBe('130');
@@ -3063,7 +3083,7 @@ describe('input', function() {
30633083
});
30643084

30653085
it('should validate', function() {
3066-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" max="10" />');
3086+
var inputElm = compileRangeInput('ng-model="value" name="alias" max="10"');
30673087

30683088
helper.changeInputValueTo('20');
30693089
expect(inputElm).toBeValid();
@@ -3079,7 +3099,7 @@ describe('input', function() {
30793099
it('should set the model to the max val if it is greater than the max val', function() {
30803100
scope.value = 110;
30813101
// Default max is 100
3082-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" max="{{max}}" />');
3102+
var inputElm = compileRangeInput('ng-model="value" name="alias" max="{{max}}"');
30833103

30843104
expect(inputElm).toBeValid();
30853105
expect(inputElm.val()).toBe('100');
@@ -3094,7 +3114,7 @@ describe('input', function() {
30943114

30953115
it('should adjust the element and model value if the max value changes on-the-fly', function() {
30963116
scope.max = 10;
3097-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" max="{{max}}" />');
3117+
var inputElm = compileRangeInput('ng-model="value" name="alias" max="{{max}}"');
30983118

30993119
helper.changeInputValueTo('5');
31003120
expect(inputElm).toBeValid();
@@ -3126,7 +3146,7 @@ describe('input', function() {
31263146

31273147
} else {
31283148
it('should validate if "range" is not implemented', function() {
3129-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" max="10" />');
3149+
var inputElm = compileRangeInput('ng-model="value" name="alias" max="10"');
31303150

31313151
helper.changeInputValueTo('20');
31323152
expect(inputElm).toBeInvalid();
@@ -3141,7 +3161,7 @@ describe('input', function() {
31413161

31423162
it('should not assume a max val of 100 if the max attribute interpolates to a non-number', function() {
31433163
scope.value = 120;
3144-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" max="{{max}}" />');
3164+
var inputElm = compileRangeInput('ng-model="value" name="alias" max="{{max}}"');
31453165

31463166
expect(inputElm).toBeValid();
31473167
expect(inputElm.val()).toBe('120');
@@ -3169,7 +3189,7 @@ describe('input', function() {
31693189

31703190
it('should validate even if the max value changes on-the-fly', function() {
31713191
scope.max = 10;
3172-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" max="{{max}}" />');
3192+
var inputElm = compileRangeInput('ng-model="value" name="alias" max="{{max}}"');
31733193

31743194
helper.changeInputValueTo('5');
31753195
expect(inputElm).toBeValid();
@@ -3209,15 +3229,15 @@ describe('input', function() {
32093229
it('should set the correct initial value when min and max are specified', function() {
32103230
scope.max = 80;
32113231
scope.min = 40;
3212-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" max="{{max}}" min="{{min}}" />');
3232+
var inputElm = compileRangeInput('ng-model="value" name="alias" max="{{max}}" min="{{min}}"');
32133233

32143234
expect(inputElm.val()).toBe('60');
32153235
expect(scope.value).toBe(60);
32163236
});
32173237

32183238
it('should set element and model value to min if max is less than min', function() {
32193239
scope.min = 40;
3220-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" max="{{max}}" min="{{min}}" />');
3240+
var inputElm = compileRangeInput('ng-model="value" name="alias" max="{{max}}" min="{{min}}"');
32213241

32223242
expect(inputElm.val()).toBe('70');
32233243
expect(scope.value).toBe(70);
@@ -3240,7 +3260,7 @@ describe('input', function() {
32403260
// However, currently only Firefox fully inplements the spec when setting the value after the step value changes.
32413261
// Other browsers fail in various edge cases, which is why they are not tested here.
32423262
it('should round the input value to the nearest step on user input', function() {
3243-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" step="5" />');
3263+
var inputElm = compileRangeInput('ng-model="value" name="alias" step="5"');
32443264

32453265
helper.changeInputValueTo('5');
32463266
expect(inputElm).toBeValid();
@@ -3269,7 +3289,7 @@ describe('input', function() {
32693289
});
32703290

32713291
it('should round the input value to the nearest step when setting the model', function() {
3272-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" step="5" />');
3292+
var inputElm = compileRangeInput('ng-model="value" name="alias" step="5"');
32733293

32743294
scope.$apply('value = 10');
32753295
expect(inputElm.val()).toBe('10');
@@ -3306,7 +3326,7 @@ describe('input', function() {
33063326
it('should validate if "range" is not implemented', function() {
33073327
scope.step = 10;
33083328
scope.value = 20;
3309-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" step="{{step}}" />');
3329+
var inputElm = compileRangeInput('ng-model="value" name="alias" step="{{step}}"');
33103330

33113331
expect(inputElm.val()).toBe('20');
33123332
expect(inputElm).toBeValid();
@@ -3334,7 +3354,7 @@ describe('input', function() {
33343354

33353355
it('should validate even if the step value changes on-the-fly', function() {
33363356
scope.step = 10;
3337-
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" step="{{step}}" />');
3357+
var inputElm = compileRangeInput('ng-model="value" name="alias" step="{{step}}"');
33383358

33393359
helper.changeInputValueTo('10');
33403360
expect(inputElm).toBeValid();
@@ -3377,6 +3397,11 @@ describe('input', function() {
33773397
});
33783398
}
33793399
});
3400+
3401+
// Helpers
3402+
function compileRangeInput(attrs, opts) {
3403+
return helper.compileInput('<input type="range" ng-input-range ' + attrs + ' />', opts);
3404+
}
33803405
});
33813406

33823407
describe('email', function() {

0 commit comments

Comments
 (0)