diff --git a/src/ng/directive/select.js b/src/ng/directive/select.js
index 9150d26cdb09..f65f3a663c2d 100644
--- a/src/ng/directive/select.js
+++ b/src/ng/directive/select.js
@@ -355,6 +355,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
valuesFn = $parse(match[7]),
track = match[8],
trackFn = track ? $parse(match[8]) : null,
+ trackKeysCache = {},
// This is an array of array of existing option groups in DOM.
// We try to reuse these if possible
// - optionGroupsCache[0] is the options with no option group
@@ -405,10 +406,11 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
if (multiple) {
viewValue = [];
forEach(selectElement.val(), function(selectedKey) {
+ selectedKey = trackFn ? trackKeysCache[selectedKey] : selectedKey;
viewValue.push(getViewValue(selectedKey, collection[selectedKey]));
});
} else {
- var selectedKey = selectElement.val();
+ var selectedKey = trackFn ? trackKeysCache[selectElement.val()] : selectElement.val();
viewValue = getViewValue(selectedKey, collection[selectedKey]);
}
ctrl.$setViewValue(viewValue);
@@ -530,7 +532,10 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
anySelected = false,
lastElement,
element,
- label;
+ label,
+ optionId;
+
+ trackKeysCache = {};
// We now build up the list of options we need (we merge later)
for (index = 0; length = keys.length, index < length; index++) {
@@ -554,9 +559,14 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
// doing displayFn(scope, locals) || '' overwrites zero values
label = isDefined(label) ? label : '';
+ optionId = trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index);
+ if (trackFn) {
+ trackKeysCache[optionId] = key;
+ }
+
optionGroup.push({
// either the index into array or key from object
- id: (keyName ? keys[index] : index),
+ id: optionId,
label: label,
selected: selected // determine if we should be selected
});
diff --git a/test/ng/directive/selectSpec.js b/test/ng/directive/selectSpec.js
index 3e918fc80309..7aa91b97fcfb 100644
--- a/test/ng/directive/selectSpec.js
+++ b/test/ng/directive/selectSpec.js
@@ -722,10 +722,45 @@ describe('select', function() {
describe('trackBy expression', function() {
beforeEach(function() {
scope.arr = [{id: 10, label: 'ten'}, {id:20, label: 'twenty'}];
- scope.obj = {'10': {score: 10, label: 'ten'}, '20': {score: 20, label: 'twenty'}};
+ scope.obj = {'1': {score: 10, label: 'ten'}, '2': {score: 20, label: 'twenty'}};
});
+ it('should set the result of track by expression to element value', function() {
+ createSelect({
+ 'ng-model': 'selected',
+ 'ng-options': 'item.label for item in arr track by item.id'
+ });
+
+ scope.$apply(function() {
+ scope.selected = scope.arr[0];
+ });
+ expect(element.val()).toBe('10');
+
+ scope.$apply(function() {
+ scope.arr[0] = {id: 10, label: 'new ten'};
+ });
+ expect(element.val()).toBe('10');
+
+ element.children()[1].selected = 'selected';
+ browserTrigger(element, 'change');
+ expect(scope.selected).toEqual(scope.arr[1]);
+ });
+
+
+ it('should use the tracked expression as option value', function() {
+ createSelect({
+ 'ng-model': 'selected',
+ 'ng-options': 'item.label for item in arr track by item.id'
+ });
+
+ var options = element.find('option');
+ expect(options.length).toEqual(3);
+ expect(sortedHtml(options[0])).toEqual('');
+ expect(sortedHtml(options[1])).toEqual('');
+ expect(sortedHtml(options[2])).toEqual('');
+ });
+
it('should preserve value even when reference has changed (single&array)', function() {
createSelect({
'ng-model': 'selected',
@@ -735,12 +770,12 @@ describe('select', function() {
scope.$apply(function() {
scope.selected = scope.arr[0];
});
- expect(element.val()).toBe('0');
+ expect(element.val()).toBe('10');
scope.$apply(function() {
scope.arr[0] = {id: 10, label: 'new ten'};
});
- expect(element.val()).toBe('0');
+ expect(element.val()).toBe('10');
element.children()[1].selected = 1;
browserTrigger(element, 'change');
@@ -758,12 +793,12 @@ describe('select', function() {
scope.$apply(function() {
scope.selected = scope.arr;
});
- expect(element.val()).toEqual(['0','1']);
+ expect(element.val()).toEqual(['10','20']);
scope.$apply(function() {
scope.arr[0] = {id: 10, label: 'new ten'};
});
- expect(element.val()).toEqual(['0','1']);
+ expect(element.val()).toEqual(['10','20']);
element.children()[0].selected = false;
browserTrigger(element, 'change');
@@ -778,18 +813,18 @@ describe('select', function() {
});
scope.$apply(function() {
- scope.selected = scope.obj['10'];
+ scope.selected = scope.obj['1'];
});
expect(element.val()).toBe('10');
scope.$apply(function() {
- scope.obj['10'] = {score: 10, label: 'ten'};
+ scope.obj['1'] = {score: 10, label: 'ten'};
});
expect(element.val()).toBe('10');
element.val('20');
browserTrigger(element, 'change');
- expect(scope.selected).toBe(scope.obj[20]);
+ expect(scope.selected).toBe(scope.obj['2']);
});
@@ -801,18 +836,18 @@ describe('select', function() {
});
scope.$apply(function() {
- scope.selected = [scope.obj['10']];
+ scope.selected = [scope.obj['1']];
});
expect(element.val()).toEqual(['10']);
scope.$apply(function() {
- scope.obj['10'] = {score: 10, label: 'ten'};
+ scope.obj['1'] = {score: 10, label: 'ten'};
});
expect(element.val()).toEqual(['10']);
element.children()[1].selected = 'selected';
browserTrigger(element, 'change');
- expect(scope.selected).toEqual([scope.obj[10], scope.obj[20]]);
+ expect(scope.selected).toEqual([scope.obj['1'], scope.obj['2']]);
});
});
@@ -840,16 +875,16 @@ describe('select', function() {
scope.$apply(function() {
scope.selected = scope.arr[0].subItem;
});
- expect(element.val()).toEqual('0');
+ expect(element.val()).toEqual('10');
scope.$apply(function() {
scope.selected = scope.arr[1].subItem;
});
- expect(element.val()).toEqual('1');
+ expect(element.val()).toEqual('20');
// Now test view -> model
- element.val('0');
+ element.val('10');
browserTrigger(element, 'change');
expect(scope.selected).toBe(scope.arr[0].subItem);
@@ -861,7 +896,7 @@ describe('select', function() {
subItem: {label: 'new twenty', id: 20}
}];
});
- expect(element.val()).toBe('0');
+ expect(element.val()).toBe('10');
expect(scope.selected.id).toBe(10);
});
@@ -879,12 +914,12 @@ describe('select', function() {
scope.$apply(function() {
scope.selected = [scope.arr[0].subItem];
});
- expect(element.val()).toEqual(['0']);
+ expect(element.val()).toEqual(['10']);
scope.$apply(function() {
scope.selected = [scope.arr[1].subItem];
});
- expect(element.val()).toEqual(['1']);
+ expect(element.val()).toEqual(['20']);
// Now test view -> model
@@ -901,7 +936,7 @@ describe('select', function() {
subItem: {label: 'new twenty', id: 20}
}];
});
- expect(element.val()).toEqual(['0']);
+ expect(element.val()).toEqual(['10']);
expect(scope.selected[0].id).toEqual(10);
expect(scope.selected.length).toBe(1);
});
@@ -1338,20 +1373,20 @@ describe('select', function() {
scope.selected = scope.values[1];
});
- expect(element.val()).toEqual('1');
+ expect(element.val()).toEqual('2');
var first = jqLite(element.find('option')[0]);
expect(first.text()).toEqual('first');
- expect(first.attr('value')).toEqual('0');
+ expect(first.attr('value')).toEqual('1');
var forth = jqLite(element.find('option')[3]);
expect(forth.text()).toEqual('forth');
- expect(forth.attr('value')).toEqual('3');
+ expect(forth.attr('value')).toEqual('4');
scope.$apply(function() {
scope.selected = scope.values[3];
});
- expect(element.val()).toEqual('3');
+ expect(element.val()).toEqual('4');
});