49

For watching an object scope variable, is $scope.$watch with objectEquality set to true OR $scope.$watchCollection better?

For a $scope object variable (like 15 attributes, some nested 2 levels deep) updated with input elements and ng-model in the view, how bad is $scope.$watch with objectEquality set to true? Is this a big thing to avoid?

Is $watchCollection a better solution?

I am looking for easy wins to improve performance on my AngularJS App (I'm still stuck on v1.2.2).

  // ctrl scope var
  $scope.filters = {
    name: '',
    info: {test: '', foo: '', bar: ''},
    yep: ''
    // etc ...
  }

  // ctrl watch ?
  $scope.$watch('filters', function(newVal, oldVal) {
    if(newVal !== oldVal) {
      // call with updated filters
    }
  }, true);

  // or ctrl watch collection ?
  $scope.$watchCollection('filters', function(newVal, oldVal) {
    if(newVal !== oldVal) {
      // call with updated filters
    }
  });

  // view input with ng-model
  <input type="text" ng-model="filters.name" />
  <input type="text" ng-model="filters.info.test" />
  <input type="text" ng-model="filters.yep" />
  // etc ...  
danwellman
  • 9,068
  • 8
  • 60
  • 88
user12121234
  • 2,519
  • 2
  • 25
  • 27

4 Answers4

201

$watch() will be triggered by:

$scope.myArray = [];
$scope.myArray = null;
$scope.myArray = someOtherArray;

$watchCollection() will be triggered by everything above AND:

$scope.myArray.push({}); // add element
$scope.myArray.splice(0, 1); // remove element
$scope.myArray[0] = {}; // assign index to different value

$watch(..., true) will be triggered by EVERYTHING above AND:

$scope.myArray[0].someProperty = "someValue";

JUST ONE MORE THING...

$watch() is the only one that fires when an array is replaced with another with the same exact content. For example:

$scope.myArray = ["Apples", "Bananas", "Orange" ];

var newArray = [];
newArray.push("Apples");
newArray.push("Bananas");
newArray.push("Orange");

$scope.myArray = newArray;

Below is a link to an example JSFiddle that uses all the different watch combinations and outputs log messages to indicate which "watches" were triggered:

http://jsfiddle.net/luisperezphd/2zj9k872/

Luis Perez
  • 27,650
  • 10
  • 79
  • 80
  • Not sure what you mean by `$watchCollection(...,true)`, as the function doesn't take boolean argument (ie. objectEquality) like `$watch`. – tamakisquare Nov 01 '15 at 09:35
  • @tamakisquare I made the correction and additional updates to make it clearer. Including updating the JS Fiddle. – Luis Perez Nov 01 '15 at 21:20
  • @luisperezphd - Regarding your latest update, `$watchCollection()` also gets fired, because both `$watch()` and `$watchCollection()` compare object references, instead of comparing object equality like in `$watch(... true)` – tamakisquare Nov 02 '15 at 10:02
  • @tamakisquare can you create a JS Fiddle to illustrate this. I have this scenario in the JS Fiddle from my answer and `$watchCollection()` is not firing. The example is using AngularJS 1.2.1. – Luis Perez Nov 03 '15 at 00:23
  • @luisperezphd - I think I must have got mixed up with something else last night when I was trying out `$watchCollection`, because I can't reproduce that with `$watchCollection()` now just like you said. Sorry for the false alarm. My bad. – tamakisquare Nov 03 '15 at 06:12
41

The $watchCollection() function is a sort-of mid-ground between the two $watch() configurations above. It's more in-depth than the vanilla $watch() function; but, it's not nearly as expensive as the deep-equality $watch() function. Like the $watch() function, the $watchCollection() works by comparing physical object references; however, unlike the $watch() function, the $watchCollection() goes one-level deep and performs an additional, shallow reference check of the top level items in the collection.

see this explanation

Mosh Feu
  • 28,354
  • 16
  • 88
  • 135
Narek Mamikonyan
  • 4,601
  • 2
  • 24
  • 30
4

$watchCollection is optimized for vector arrays [] where elements can be push

and $watch is good for associative arrays objects {}

$watchCollection will not watch for depth changes, is like watch with objectEquality set to false.

If you already know to structure of the depth you can optimize like this:

  // ctrl watch ?
  $scope.$watch('filters', function(newVal, oldVal) {
    if(newVal !== oldVal) {
      // call with updated filters
    }
  });

  // ctrl watch ?
  $scope.$watch('filters.info', function(newVal, oldVal) {
    if(newVal !== oldVal) {
      // call with updated filters
    }
  });
Shashank Agrawal
  • 25,161
  • 11
  • 89
  • 121
0

I know this is a suuuper late response, but might be useful for someone

Another option if you want to trigger the watcher only on when an element is added to or removed from the array is

$scope.$watch('filters.length', ...)

Take into account this will not trigger when

$scope.filters = []
$scope.filters = $scope.filters.filter(...)

or any other option that replaces the array reference.

Daro
  • 1