diff --git a/src/ng/directive/ngOptions.js b/src/ng/directive/ngOptions.js index 476da491d755..2d51cc74f4fe 100644 --- a/src/ng/directive/ngOptions.js +++ b/src/ng/directive/ngOptions.js @@ -32,8 +32,9 @@ var ngOptionsMinErr = minErr('ngOptions'); * option. See example below for demonstration. * *
- * **Note:** `ngModel` compares by reference, not value. This is important when binding to an - * array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/). + * **Note:** By default, `ngModel` compares by reference, not value. This is important when binding to an + * array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/). When using `track by` + * in an `ngOptions` expression, however, deep equality checks will be performed. *
* * ## `select` **`as`** @@ -275,6 +276,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) { } return { + trackBy: trackBy, getWatchables: $parse(valuesFn, function(values) { // Create a collection of things that we would like to watch (watchedArray) // so that they can all be watched using a single $watchCollection @@ -500,8 +502,9 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) { // We also need to watch to see if the internals of the model changes, since // ngModel only watches for object identity change - scope.$watch(attr.ngModel, function() { ngModelCtrl.$render(); }, true); - + if (ngOptions.trackBy) { + scope.$watch(attr.ngModel, function() { ngModelCtrl.$render(); }, true); + } // ------------------------------------------------------------------ // diff --git a/src/ng/directive/select.js b/src/ng/directive/select.js index b0b69aca1f83..6533fe8906c5 100644 --- a/src/ng/directive/select.js +++ b/src/ng/directive/select.js @@ -135,8 +135,9 @@ var SelectController = * option. See example below for demonstration. * *
- * **Note:** `ngModel` compares by reference, not value. This is important when binding to an - * array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/). + * **Note:** By default, `ngModel` compares by reference, not value. This is important when binding to an + * array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/). When using `track by` + * in an `ngOptions` expression, however, deep equality checks will be performed. *
* */ diff --git a/test/ng/directive/ngOptionsSpec.js b/test/ng/directive/ngOptionsSpec.js index ceb3e312f494..b6187a3d3030 100644 --- a/test/ng/directive/ngOptionsSpec.js +++ b/test/ng/directive/ngOptionsSpec.js @@ -1041,6 +1041,50 @@ describe('ngOptions', function() { }); }).not.toThrow(); }); + + it('should setup equality watches on ngModel changes if using trackBy', function() { + + createSelect({ + 'ng-model': 'selected', + 'ng-options': 'item for item in arr track by item.id' + }); + + scope.$apply(function() { + scope.selected = scope.arr[0]; + }); + + spyOn(element.controller('ngModel'), '$render'); + + scope.$apply(function() { + scope.selected.label = 'changed'; + }); + + // update render due to equality watch + expect(element.controller('ngModel').$render).toHaveBeenCalled(); + + }); + + it('should not setup equality watches on ngModel changes if not using trackBy', function() { + + createSelect({ + 'ng-model': 'selected', + 'ng-options': 'item for item in arr' + }); + + scope.$apply(function() { + scope.selected = scope.arr[0]; + }); + + spyOn(element.controller('ngModel'), '$render'); + + scope.$apply(function() { + scope.selected.label = 'changed'; + }); + + // no render update as no equality watch + expect(element.controller('ngModel').$render).not.toHaveBeenCalled(); + }); + }); @@ -1657,7 +1701,7 @@ describe('ngOptions', function() { scope.values.pop(); }); - expect(element.val()).toEqual(''); + expect(element.val()).toEqualUnknownValue(); expect(scope.selected).toEqual(null); // Check after model change @@ -1671,7 +1715,7 @@ describe('ngOptions', function() { scope.values.pop(); }); - expect(element.val()).toEqual(''); + expect(element.val()).toEqualUnknownValue(); expect(scope.selected).toEqual(null); });