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`**
  *
- * <div class="alert alert-warning">
- * Be careful when using `select` **`as`** and **`track by`** in the same expression.
- * </div>
+ * 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 `<option>` and the `<select>` 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
+ * <select ng-options="item.subItem as item.label for item in items track by $value.id" ng-model="selected"></select>
+ * ```
+ * ```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