How to deep watch an array in angularjs?

There is an array of objects in my scope, I want to watch all the values of each object.

This is my code:

function TodoCtrl($scope) {
  $scope.columns = [
      { field:'title', displayName: 'TITLE'},
      { field: 'content', displayName: 'CONTENT' }
  ];
   $scope.$watch('columns', function(newVal) {
       alert('columns changed');
   });
}

But when I modify the values, e.g. I change TITLE to TITLE2, the alert('columns changed') never popped.

How to deep watch the objects inside an array?

There is a live demo: http://jsfiddle.net/SYx9b/

You can set the 3rd argument of $watch to true:

scope.$watch('data', function (newVal, oldVal) { /*...*/ }, true);

See http://docs.angularjs.org/api/ng.$rootScope.Scope#$watch

Since Angular 1.1.x you can also use $watchCollection to watch shallow watch (just the "first level" of) the collection.

scope.$watchCollection('data', function (newVal, oldVal) { /*...*/ });

See https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watchCollection

There are performance consequences to deep-diving an object in your $watch. Sometimes (for example, when changes are only pushes and pops), you might want to $watch an easily calculated value, such as array.length.

If you're going to watch only one array, you can simply use this bit of code:

$scope.$watch('columns', function() {
  // some value in the array has changed 
}, true); // watching properties

example

But this will not work with multiple arrays:

$scope.$watch('columns + ANOTHER_ARRAY', function() {
  // will never be called when things change in columns or ANOTHER_ARRAY
}, true);

example

To handle this situation, I usually convert the multiple arrays I want to watch into JSON:

$scope.$watch(function() { 
  return angular.toJson([$scope.columns, $scope.ANOTHER_ARRAY, ... ]); 
},
function() {
  // some value in some array has changed
}

example

As @jssebastian pointed out in the comments, JSON.stringify may be preferable to angular.toJson as it can handle members that start with '$' and possible other cases as well.

It's worth noting that in Angular 1.1.x and above, you can now use $watchCollection rather than $watch. Although the $watchCollection appears to create shallow watches so it won't work with arrays of objects like you expect. It can detect additions and deletions to the array, but not the properties of objects inside arrays.

$watchCollection accomplishes what you want to do. Below is an example copied from angularjs website http://docs.angularjs.org/api/ng/type/$rootScope.Scope While it's convenient, the performance needs to be taken into consideration especially when you watch a large collection.

   $scope.names = ['igor', 'matias', 'misko', 'james'];
      $scope.dataCount = 4;

      $scope.$watchCollection('names', function(newNames, oldNames) {
        $scope.dataCount = newNames.length;
      });

      expect($scope.dataCount).toEqual(4);
      $scope.$digest();

      //still at 4 ... no changes
      expect($scope.dataCount).toEqual(4);

      $scope.names.pop();
      $scope.$digest();

      //now there's been a change
      expect($scope.dataCount).toEqual(3);

In my case, I needed to watch a service, which contains an address object also watched by several other controllers. I was stuck in a loop until I added the 'true' parameter, which seems to be the key to success when watching objects.

$scope.$watch(function() {
    return LocationService.getAddress();
}, function(address) {
    //handle address object
}, true);

This solution worked very well for me, i'm doing this in a directive:

scope.$watch(attrs.testWatch, function() {.....}, true);

the true works pretty well and react for all the chnages (add, delete, or modify a field).

Here is a working plunker for play with it.

Deeply Watching an Array in AngularJS

I hope this can be useful for you. If you have any questions, feel free for ask, I'll try to help :)