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 https://docs.angularjs.org/api/ng/type/$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.
Here is a comparison of the 3 ways you can watch a scope variable with examples:
$watch() is triggered by:
$scope.myArray = [];
$scope.myArray = null;
$scope.myArray = someOtherArray;
$watchCollection() is 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) is triggered by EVERYTHING above AND:
$scope.myArray[0].someProperty = "someValue";
JUST ONE MORE THING...
$watch() is the only one that triggers when an array is replaced with another array even if that other array has the same exact content.
For example where $watch()
would fire and $watchCollection()
would not:
$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/