Skip to content

Commit 82b0929

Browse files
committed
fix(select): update option if interpolated value attribute changes
This is for options added without ngOptions. Previously, an option with an interpolated value attribute would not be updated if the binding changed, i.e. the select controller would not recognize the changed option. Now the value attribute will be observed if it contains an interpolation. Closes angular#12005 Closes angular#12582
1 parent 7d2c6ee commit 82b0929

2 files changed

Lines changed: 87 additions & 10 deletions

File tree

src/ng/directive/select.js

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -401,9 +401,12 @@ var optionDirective = ['$interpolate', function($interpolate) {
401401
priority: 100,
402402
compile: function(element, attr) {
403403

404-
// If the value attribute is not defined then we fall back to the
405-
// text content of the option element, which may be interpolated
406-
if (isUndefined(attr.value)) {
404+
if (isDefined(attr.value)) {
405+
// If the value attribute is defined, check if it contains an interpolation
406+
var valueInterpolated = $interpolate(attr.value, true);
407+
} else {
408+
// If the value attribute is not defined then we fall back to the
409+
// text content of the option element, which may be interpolated
407410
var interpolateFn = $interpolate(element.text(), true);
408411
if (!interpolateFn) {
409412
attr.$set('value', element.text());
@@ -419,24 +422,38 @@ var optionDirective = ['$interpolate', function($interpolate) {
419422
selectCtrl = parent.data(selectCtrlName) ||
420423
parent.parent().data(selectCtrlName); // in case we are in optgroup
421424

425+
function addOption(optionValue) {
426+
selectCtrl.addOption(optionValue, element);
427+
selectCtrl.ngModelCtrl.$render();
428+
chromeHack(element);
429+
}
430+
422431
// Only update trigger option updates if this is an option within a `select`
423432
// that also has `ngModel` attached
424433
if (selectCtrl && selectCtrl.ngModelCtrl) {
425434

426-
if (interpolateFn) {
435+
if (valueInterpolated) {
436+
// The value attribute is interpolated
437+
var oldVal;
438+
attr.$observe('value', function valueAttributeObserveAction(newVal) {
439+
if (isDefined(oldVal)) {
440+
selectCtrl.removeOption(oldVal);
441+
}
442+
oldVal = newVal;
443+
addOption(newVal);
444+
});
445+
} else if (interpolateFn) {
446+
// The text content is interpolated
427447
scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) {
428448
attr.$set('value', newVal);
429449
if (oldVal !== newVal) {
430450
selectCtrl.removeOption(oldVal);
431451
}
432-
selectCtrl.addOption(newVal, element);
433-
selectCtrl.ngModelCtrl.$render();
434-
chromeHack(element);
452+
addOption(newVal);
435453
});
436454
} else {
437-
selectCtrl.addOption(attr.value, element);
438-
selectCtrl.ngModelCtrl.$render();
439-
chromeHack(element);
455+
// The value attribute is static
456+
addOption(attr.value);
440457
}
441458

442459
element.on('$destroy', function() {

test/ng/directive/selectSpec.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,66 @@ describe('select', function() {
980980
expect(element).toEqualSelect(['hello']);
981981
});
982982

983+
984+
it('should add options with interpolated value attributes',
985+
inject(function($rootScope, $compile) {
986+
var scope = $rootScope;
987+
988+
scope.option1 = 'option1';
989+
scope.option2 = 'option2';
990+
991+
var element = $compile(
992+
'<select ng-model="selected">' +
993+
'<option value="{{option1}}">Option 1</option>' +
994+
'<option value="{{option2}}">Option 2</option>' +
995+
'</div>')(scope);
996+
997+
scope.$digest();
998+
expect(scope.selected).toBeUndefined();
999+
1000+
browserTrigger(element.find('option').eq(0));
1001+
expect(scope.selected).toBe('option1');
1002+
1003+
scope.selected = 'option2';
1004+
scope.$digest();
1005+
expect(element.find('option').eq(1).prop('selected')).toBe(true);
1006+
expect(element.find('option').eq(1).text()).toBe('Option 2');
1007+
})
1008+
);
1009+
1010+
1011+
it('should update the option when the interpolated value attribute changes',
1012+
inject(function($rootScope, $compile) {
1013+
var scope = $rootScope;
1014+
1015+
scope.option1 = 'option1';
1016+
scope.option2 = '';
1017+
1018+
var element = $compile(
1019+
'<select ng-model="selected">' +
1020+
'<option value="{{option1}}">Option 1</option>' +
1021+
'<option value="{{option2}}">Option 2</option>' +
1022+
'</div>')(scope);
1023+
1024+
var selectCtrl = element.controller('select');
1025+
spyOn(selectCtrl, 'removeOption').andCallThrough();
1026+
1027+
scope.$digest();
1028+
expect(scope.selected).toBeUndefined();
1029+
expect(selectCtrl.removeOption).not.toHaveBeenCalled();
1030+
1031+
//Change value of option2
1032+
scope.option2 = 'option2Changed';
1033+
scope.selected = 'option2Changed';
1034+
scope.$digest();
1035+
1036+
expect(selectCtrl.removeOption).toHaveBeenCalledWith('');
1037+
expect(element.find('option').eq(1).prop('selected')).toBe(true);
1038+
expect(element.find('option').eq(1).text()).toBe('Option 2');
1039+
})
1040+
);
1041+
1042+
9831043
it('should not blow up when option directive is found inside of a datalist',
9841044
inject(function($compile, $rootScope) {
9851045
var element = $compile('<div>' +

0 commit comments

Comments
 (0)