diff --git a/src/ng/directive/ngOptions.js b/src/ng/directive/ngOptions.js
index af963c665dc7..ee7cf32831f9 100644
--- a/src/ng/directive/ngOptions.js
+++ b/src/ng/directive/ngOptions.js
@@ -66,9 +66,7 @@ var ngOptionsMinErr = minErr('ngOptions');
*
* ### `select` **`as`** and **`track by`**
*
- *
- * Be careful when using `select` **`as`** and **`track by`** in the same expression.
- *
+ * When using `select` **`as`** and **`track by`** in the same expression use the `$value` variable.
*
* Given this array of items on the $scope:
*
@@ -110,6 +108,15 @@ var ngOptionsMinErr = minErr('ngOptions');
* expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value
* is not matched against any `` and the `` appears as having no selected value.
*
+ * The solution is to use `$value` variable which provides uniform access to each `item` and
+ * `ngModel` value. Here is the fixed version of the broken example above.
+ *
+ * ```html
+ *
+ * ```
+ * ```js
+ * $scope.selected = $scope.items[0].subItem;
+ * ```
*
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
* @param {comprehension_expression} ngOptions in one of the following forms:
@@ -285,7 +292,7 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile,
function(value, locals) { return trackByFn(scope, locals); } :
function getHashOfValue(value) { return hashKey(value); };
var getTrackByValue = function(value, key) {
- return getTrackByValueFn(value, getLocals(value, key));
+ return getTrackByValueFn(value, getLocals(value, key, true));
};
var displayFn = $parse(match[2] || match[1]);
@@ -294,12 +301,10 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile,
var valuesFn = $parse(match[8]);
var locals = {};
- var getLocals = keyName ? function(value, key) {
- locals[keyName] = key;
- locals[valueName] = value;
- return locals;
- } : function(value) {
+ var getLocals = function(value, key, isViewValue) {
+ if (keyName) locals[keyName] = key;
locals[valueName] = value;
+ locals['$value'] = isViewValue ? value : viewValueFn(value, locals);
return locals;
};
@@ -345,7 +350,7 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile,
var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
var value = optionValues[key];
- var locals = getLocals(value, key);
+ var locals = getLocals(value, key, true);
var selectValue = getTrackByValueFn(value, locals);
watchedArray.push(selectValue);
@@ -378,7 +383,7 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile,
for (var index = 0; index < optionValuesLength; index++) {
var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
var value = optionValues[key];
- var locals = getLocals(value, key);
+ var locals = getLocals(value, key, false);
var viewValue = viewValueFn(scope, locals);
var selectValue = getTrackByValueFn(viewValue, locals);
var label = displayFn(scope, locals);
diff --git a/test/ng/directive/ngOptionsSpec.js b/test/ng/directive/ngOptionsSpec.js
index 56b11d04f0d2..b7845b31a016 100644
--- a/test/ng/directive/ngOptionsSpec.js
+++ b/test/ng/directive/ngOptionsSpec.js
@@ -1509,10 +1509,6 @@ describe('ngOptions', function() {
});
- /**
- * This behavior is broken and should probably be cleaned up later as track by and select as
- * aren't compatible.
- */
describe('selectAs+trackBy expression', function() {
beforeEach(function() {
scope.arr = [{subItem: {label: 'ten', id: 10}}, {subItem: {label: 'twenty', id: 20}}];
@@ -1520,11 +1516,11 @@ describe('ngOptions', function() {
});
- it('It should use the "value" variable to represent items in the array as well as for the ' +
+ it('It should use the "$value" variable to represent items in the array as well as for the ' +
'selected values in track by expression (single&array)', function() {
createSelect({
'ng-model': 'selected',
- 'ng-options': 'item.subItem as item.subItem.label for item in arr track by (item.id || item.subItem.id)'
+ 'ng-options': 'item.subItem as item.subItem.label for item in arr track by $value.id'
});
// First test model -> view
@@ -1558,12 +1554,12 @@ describe('ngOptions', function() {
});
- it('It should use the "value" variable to represent items in the array as well as for the ' +
+ it('It should use the "$value" variable to represent items in the array as well as for the ' +
'selected values in track by expression (multiple&array)', function() {
createSelect({
'ng-model': 'selected',
'multiple': true,
- 'ng-options': 'item.subItem as item.subItem.label for item in arr track by (item.id || item.subItem.id)'
+ 'ng-options': 'item.subItem as item.subItem.label for item in arr track by $value.id'
});
// First test model -> view
@@ -1599,12 +1595,12 @@ describe('ngOptions', function() {
});
- it('It should use the "value" variable to represent items in the array as well as for the ' +
+ it('It should use the "$value" variable to represent items in the array as well as for the ' +
'selected values in track by expression (multiple&object)', function() {
createSelect({
'ng-model': 'selected',
'multiple': true,
- 'ng-options': 'val.subItem as val.subItem.label for (key, val) in obj track by (val.id || val.subItem.id)'
+ 'ng-options': 'val.subItem as val.subItem.label for (key, val) in obj track by $value.id'
});
// First test model -> view
@@ -1644,11 +1640,11 @@ describe('ngOptions', function() {
});
- it('It should use the "value" variable to represent items in the array as well as for the ' +
+ it('It should use the "$value" variable to represent items in the array as well as for the ' +
'selected values in track by expression (single&object)', function() {
createSelect({
'ng-model': 'selected',
- 'ng-options': 'val.subItem as val.subItem.label for (key, val) in obj track by (val.id || val.subItem.id)'
+ 'ng-options': 'val.subItem as val.subItem.label for (key, val) in obj track by $value.id'
});
// First test model -> view