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

Commit 2e6c7d4

Browse files
authored
fix(select): let ngValue take precedence over option text with multiple interpolations
Fixes #15413 Closes #15420
1 parent f7a716f commit 2e6c7d4

File tree

2 files changed

+81
-9
lines changed

2 files changed

+81
-9
lines changed

src/ng/directive/select.js

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,10 @@ var SelectController =
116116
};
117117

118118

119-
self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) {
119+
self.registerOption = function(optionScope, optionElement, optionAttrs, hasDynamicValueAttr, interpolateTextFn) {
120120

121-
if (interpolateValueFn) {
122-
// The value attribute is interpolated
121+
if (hasDynamicValueAttr) {
122+
// either "value" is interpolated directly, or set by ngValue
123123
var oldVal;
124124
optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
125125
if (isDefined(oldVal)) {
@@ -165,7 +165,9 @@ var SelectController =
165165
* When an item in the `<select>` menu is selected, the value of the selected option will be bound
166166
* to the model identified by the `ngModel` directive. With static or repeated options, this is
167167
* the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing.
168-
* If you want dynamic value attributes, you can use interpolation inside the value attribute.
168+
* For dynamic options, use interpolation inside the `value` attribute or the `textContent`. Using
169+
* {@link ngValue ngValue} is also possible (as it sets the `value` attribute), and will take
170+
* precedence over `value` and `textContent`.
169171
*
170172
* <div class="alert alert-warning">
171173
* Note that the value of a `select` directive used without `ngOptions` is always a string.
@@ -456,13 +458,19 @@ var optionDirective = ['$interpolate', function($interpolate) {
456458
restrict: 'E',
457459
priority: 100,
458460
compile: function(element, attr) {
459-
if (isDefined(attr.value)) {
460-
// If the value attribute is defined, check if it contains an interpolation
461-
var interpolateValueFn = $interpolate(attr.value, true);
461+
var hasDynamicValueAttr, interpolateTextFn;
462+
463+
if (isDefined(attr.ngValue)) {
464+
// If ngValue is defined, then the value attr will be set to the result of the expression,
465+
// and the selectCtrl must set up an observer
466+
hasDynamicValueAttr = true;
467+
} else if (isDefined(attr.value)) {
468+
// If the value attr contains an interpolation, the selectCtrl must set up an observer
469+
hasDynamicValueAttr = $interpolate(attr.value, true);
462470
} else {
463471
// If the value attribute is not defined then we fall back to the
464472
// text content of the option element, which may be interpolated
465-
var interpolateTextFn = $interpolate(element.text(), true);
473+
interpolateTextFn = $interpolate(element.text(), true);
466474
if (!interpolateTextFn) {
467475
attr.$set('value', element.text());
468476
}
@@ -477,7 +485,7 @@ var optionDirective = ['$interpolate', function($interpolate) {
477485
parent.parent().data(selectCtrlName); // in case we are in optgroup
478486

479487
if (selectCtrl) {
480-
selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn);
488+
selectCtrl.registerOption(scope, element, attr, hasDynamicValueAttr, interpolateTextFn);
481489
}
482490
};
483491
}

test/ng/directive/selectSpec.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,5 +1223,69 @@ describe('select', function() {
12231223
'</select>');
12241224
}).toThrowMinErr('ng','badname', 'hasOwnProperty is not a valid "option value" name');
12251225
});
1226+
1227+
describe('with ngValue', function() {
1228+
1229+
describe('setting the model to the result of the expression', function() {
1230+
1231+
it('should take precedence when the option value is interpolated', function() {
1232+
scope.Option2Value = 'Option 2 Value';
1233+
scope.Option2NgValue = 'Option 2 ngValue';
1234+
1235+
compile('<select ng-model="selected">' +
1236+
'<option>Option1 Text</option>' +
1237+
'<option ng-value="Option2NgValue" value="{{Option2Value}}">Option2 Text</option>' +
1238+
'</select>');
1239+
1240+
scope.$digest();
1241+
expect(scope.selected).toBeUndefined();
1242+
1243+
browserTrigger(element.find('option').eq(2));
1244+
1245+
expect(scope.selected).toBe('Option 2 ngValue');
1246+
dealoc(element);
1247+
});
1248+
1249+
it('should take precedence when the option text is interpolated', function() {
1250+
scope.Option2Text = 'Option 2 Text';
1251+
scope.Option2NgValue = 'Option 2 ngValue';
1252+
1253+
compile('<select ng-model="selected">' +
1254+
'<option>Option1 Text</option>' +
1255+
'<option ng-value="Option2NgValue">{{Option2Text}}</option>' +
1256+
'</select>');
1257+
1258+
scope.$digest();
1259+
expect(scope.selected).toBeUndefined();
1260+
1261+
browserTrigger(element.find('option').eq(2));
1262+
1263+
expect(scope.selected).toBe('Option 2 ngValue');
1264+
dealoc(element);
1265+
});
1266+
1267+
it('should take precedence when the option text contains more than one interpolation', function() {
1268+
scope.Option2Text1 = 'Option 2 Text Part 1';
1269+
scope.Option2Text2 = 'Option 2 Text Part 2';
1270+
scope.Option2NgValue = 'Option 2 ngValue';
1271+
1272+
compile('<select ng-model="selected">' +
1273+
'<option>Option1 Text</option>' +
1274+
'<option ng-value="Option2NgValue">{{Option2Text1}} {{Option2Text2}}</option>' +
1275+
'</select>');
1276+
1277+
scope.$digest();
1278+
expect(scope.selected).toBeUndefined();
1279+
1280+
browserTrigger(element.find('option').eq(2));
1281+
1282+
expect(scope.selected).toBe('Option 2 ngValue');
1283+
dealoc(element);
1284+
});
1285+
1286+
});
1287+
1288+
});
1289+
12261290
});
12271291
});

0 commit comments

Comments
 (0)