Skip to content

Commit cd923c2

Browse files
fix(ngOptions): fix model<->option interaction when using track by
Closes angular#10869
1 parent 97e6880 commit cd923c2

File tree

2 files changed

+78
-7
lines changed

2 files changed

+78
-7
lines changed

src/ng/directive/ngOptions.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,9 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
307307
selectValueMap: selectValueMap,
308308
getOptionFromViewValue: function(value) {
309309
return selectValueMap[getTrackByValue(value, getLocals(value))];
310+
},
311+
getViewValueFromOption: function(option) {
312+
return trackBy ? angular.copy(option.viewValue) : option.viewValue;
310313
}
311314
};
312315
}
@@ -400,7 +403,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
400403
if (selectedOption) {
401404
removeEmptyOption();
402405
removeUnknownOption();
403-
return selectedOption.viewValue;
406+
return options.getViewValueFromOption(selectedOption);
404407
}
405408
return null;
406409
};
@@ -432,7 +435,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
432435
var selectedValues = selectElement.val() || [];
433436
return selectedValues.map(function(selectedKey) {
434437
var option = options.selectValueMap[selectedKey];
435-
return option.viewValue;
438+
return options.getViewValueFromOption(option);
436439
});
437440
};
438441
}
@@ -460,6 +463,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
460463

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

464468
// ------------------------------------------------------------------ //
465469

test/ng/directive/ngOptionsSpec.js

+72-5
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,74 @@ describe('ngOptions', function() {
656656
expect(options.eq(2)).toEqualTrackedOption(20, 'twenty');
657657
});
658658

659-
659+
660+
it('should update the selected item when only the properties on the object change (single)', function() {
661+
createSelect({
662+
'ng-model': 'selected',
663+
'ng-options': 'item.label for item in arr track by item.id'
664+
});
665+
666+
scope.$apply(function() {
667+
scope.selected = {id: 10, label: 'new ten'};
668+
});
669+
670+
expect(element.val()).toBe('10');
671+
672+
// Update the properties on the
673+
scope.$apply(function() {
674+
scope.selected.id = 20;
675+
scope.selected.label = 'new twenty';
676+
});
677+
678+
expect(element.val()).toBe('20');
679+
});
680+
681+
682+
it('should update the selected item when only the properties on the object change (multi)', function() {
683+
createSelect({
684+
'ng-model': 'selected',
685+
'multiple': true,
686+
'ng-options': 'item.label for item in arr track by item.id'
687+
});
688+
689+
scope.$apply(function() {
690+
scope.selected = [{id: 10, label: 'new ten'}];
691+
});
692+
693+
expect(element.val()).toEqual(['10']);
694+
695+
// Update the properties on the
696+
scope.$apply(function() {
697+
scope.selected[0].id = 20;
698+
scope.selected[0].label = 'new twenty';
699+
});
700+
701+
expect(element.val()).toEqual(['20']);
702+
});
703+
704+
705+
it('should prevent changes to the selected object from modifying the options objects (single)', function() {
706+
707+
createSelect({
708+
'ng-model': 'selected',
709+
'ng-options': 'item.label for item in arr track by item.id'
710+
});
711+
712+
element.val('10');
713+
browserTrigger(element, 'change');
714+
715+
expect(scope.selected).toEqual(scope.arr[0]);
716+
717+
scope.$apply(function() {
718+
scope.selected.id = 20;
719+
});
720+
721+
expect(scope.selected).not.toEqual(scope.arr[0]);
722+
expect(element.val()).toEqual('20');
723+
expect(scope.arr).toEqual([{id: 10, label: 'ten'}, {id:20, label: 'twenty'}]);
724+
});
725+
726+
660727
it('should preserve value even when reference has changed (single&array)', function() {
661728
createSelect({
662729
'ng-model': 'selected',
@@ -719,7 +786,7 @@ describe('ngOptions', function() {
719786
expect(element.val()).toBe('10');
720787

721788
setSelectValue(element, 1);
722-
expect(scope.selected).toBe(scope.obj['2']);
789+
expect(scope.selected).toEqual(scope.obj['2']);
723790
});
724791

725792

@@ -798,7 +865,7 @@ describe('ngOptions', function() {
798865

799866
element.val('10');
800867
browserTrigger(element, 'change');
801-
expect(scope.selected).toBe(scope.arr[0].subItem);
868+
expect(scope.selected).toEqual(scope.arr[0].subItem);
802869

803870
// Now reload the array
804871
scope.$apply(function() {
@@ -1352,7 +1419,7 @@ describe('ngOptions', function() {
13521419
scope.values.pop();
13531420
});
13541421

1355-
expect(element.val()).toEqualUnknownValue();
1422+
expect(element.val()).toEqual('');
13561423
expect(scope.selected).toEqual(null);
13571424

13581425
// Check after model change
@@ -1366,7 +1433,7 @@ describe('ngOptions', function() {
13661433
scope.values.pop();
13671434
});
13681435

1369-
expect(element.val()).toEqualUnknownValue();
1436+
expect(element.val()).toEqual('');
13701437
expect(scope.selected).toEqual(null);
13711438
});
13721439

0 commit comments

Comments
 (0)