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

Commit 63d5819

Browse files
fix(ngOptions): fix model<->option interaction when using track by
Closes #10869
1 parent 571c57d commit 63d5819

File tree

2 files changed

+91
-7
lines changed

2 files changed

+91
-7
lines changed

src/ng/directive/ngOptions.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,9 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
335335
selectValueMap: selectValueMap,
336336
getOptionFromViewValue: function(value) {
337337
return selectValueMap[getTrackByValue(value, getLocals(value))];
338+
},
339+
getViewValueFromOption: function(option) {
340+
return trackBy ? angular.copy(option.viewValue) : option.viewValue;
338341
}
339342
};
340343
}
@@ -428,7 +431,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
428431
if (selectedOption && !selectedOption.disabled) {
429432
removeEmptyOption();
430433
removeUnknownOption();
431-
return selectedOption.viewValue;
434+
return options.getViewValueFromOption(selectedOption);
432435
}
433436
return null;
434437
};
@@ -462,7 +465,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
462465

463466
forEach(selectedValues, function(value) {
464467
var option = options.selectValueMap[value];
465-
if (!option.disabled) selections.push(option.viewValue);
468+
if (!option.disabled) selections.push(options.getViewValueFromOption(option));
466469
});
467470

468471
return selections;
@@ -492,6 +495,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
492495

493496
// We will re-render the option elements if the option values or labels change
494497
scope.$watchCollection(ngOptions.getWatchables, updateOptions);
498+
scope.$watch(attr.ngModel, function() { ngModelCtrl.$render(); }, true);
495499

496500
// ------------------------------------------------------------------ //
497501

test/ng/directive/ngOptionsSpec.js

+85-5
Original file line numberDiff line numberDiff line change
@@ -856,7 +856,87 @@ describe('ngOptions', function() {
856856
expect(options.eq(2)).toEqualTrackedOption(20, 'twenty');
857857
});
858858

859-
859+
860+
it('should update the selected option even if only the tracked property on the selected object changes (single)', function() {
861+
createSelect({
862+
'ng-model': 'selected',
863+
'ng-options': 'item.label for item in arr track by item.id'
864+
});
865+
866+
scope.$apply(function() {
867+
scope.selected = {id: 10, label: 'ten'};
868+
});
869+
870+
expect(element.val()).toEqual('10');
871+
872+
// Update the properties on the selected object, rather than replacing the whole object
873+
scope.$apply(function() {
874+
scope.selected.id = 20;
875+
scope.selected.label = 'new twenty';
876+
});
877+
878+
// The value of the select should change since the id property changed
879+
expect(element.val()).toEqual('20');
880+
881+
// But the label of the selected option does not change
882+
var option = element.find('option').eq(1);
883+
expect(option.prop('selected')).toEqual(true);
884+
expect(option.text()).toEqual('twenty'); // not 'new twenty'
885+
});
886+
887+
888+
it('should update the selected options even if only the tracked properties on the objects in the ' +
889+
'selected collection change (multi)', function() {
890+
createSelect({
891+
'ng-model': 'selected',
892+
'multiple': true,
893+
'ng-options': 'item.label for item in arr track by item.id'
894+
});
895+
896+
scope.$apply(function() {
897+
scope.selected = [{id: 10, label: 'ten'}];
898+
});
899+
900+
expect(element.val()).toEqual(['10']);
901+
902+
// Update the properties on the object in the selected array, rather than replacing the whole object
903+
scope.$apply(function() {
904+
scope.selected[0].id = 20;
905+
scope.selected[0].label = 'new twenty';
906+
});
907+
908+
// The value of the select should change since the id property changed
909+
expect(element.val()).toEqual(['20']);
910+
911+
// But the label of the selected option does not change
912+
var option = element.find('option').eq(1);
913+
expect(option.prop('selected')).toEqual(true);
914+
expect(option.text()).toEqual('twenty'); // not 'new twenty'
915+
});
916+
917+
918+
it('should prevent changes to the selected object from modifying the options objects (single)', function() {
919+
920+
createSelect({
921+
'ng-model': 'selected',
922+
'ng-options': 'item.label for item in arr track by item.id'
923+
});
924+
925+
element.val('10');
926+
browserTrigger(element, 'change');
927+
928+
expect(scope.selected).toEqual(scope.arr[0]);
929+
930+
scope.$apply(function() {
931+
scope.selected.id = 20;
932+
});
933+
934+
expect(scope.selected).not.toEqual(scope.arr[0]);
935+
expect(element.val()).toEqual('20');
936+
expect(scope.arr).toEqual([{id: 10, label: 'ten'}, {id:20, label: 'twenty'}]);
937+
});
938+
939+
860940
it('should preserve value even when reference has changed (single&array)', function() {
861941
createSelect({
862942
'ng-model': 'selected',
@@ -919,7 +999,7 @@ describe('ngOptions', function() {
919999
expect(element.val()).toBe('10');
9201000

9211001
setSelectValue(element, 1);
922-
expect(scope.selected).toBe(scope.obj['2']);
1002+
expect(scope.selected).toEqual(scope.obj['2']);
9231003
});
9241004

9251005

@@ -998,7 +1078,7 @@ describe('ngOptions', function() {
9981078

9991079
element.val('10');
10001080
browserTrigger(element, 'change');
1001-
expect(scope.selected).toBe(scope.arr[0].subItem);
1081+
expect(scope.selected).toEqual(scope.arr[0].subItem);
10021082

10031083
// Now reload the array
10041084
scope.$apply(function() {
@@ -1577,7 +1657,7 @@ describe('ngOptions', function() {
15771657
scope.values.pop();
15781658
});
15791659

1580-
expect(element.val()).toEqualUnknownValue();
1660+
expect(element.val()).toEqual('');
15811661
expect(scope.selected).toEqual(null);
15821662

15831663
// Check after model change
@@ -1591,7 +1671,7 @@ describe('ngOptions', function() {
15911671
scope.values.pop();
15921672
});
15931673

1594-
expect(element.val()).toEqualUnknownValue();
1674+
expect(element.val()).toEqual('');
15951675
expect(scope.selected).toEqual(null);
15961676
});
15971677

0 commit comments

Comments
 (0)