AngularJS: $watch not being triggered for changes to an array of objects

All we need is an easy explanation of the problem, so here it is.

I have a table and the user can chose to filter rows in the table based on certain columns and certain values for these columns. The object structure to keep track of this filter looks like:

$scope.activeFilterAttributes = [
    {
        "columnName": "city",
        "values": ["LA", "OT", "NY"]
    },
    {
        "columnName": "weather",
        "values": ["humid", "sunny"]
    }
];

So the objects in the array contain the “columnName” and “values” key. “columnName” signifies the column to consider for the filter while “values” contains the filter values. Basically, the above array will result in rows in the table for which the city column contains “LA”, “OT” or “NY” as values and the weather column contains “humid” or “sunny” as the values. Other rows which do not contain these values are not shown.

To help understand this object better, if the user wishes to see only those rows who have “LA” or “NY” in the column for “city”, the resultant array will look like:

$scope.activeFilterAttributes = [
    {
        "columnName": "city",
        "values": ["LA", "NY"]
    },
    {
        "columnName": "weather",
        "values": []
    }
];

The user sets or removes these filters. Whenever this happens, the above array is updated.
This update happens correctly and I have verified it – no problem here.

The problem lies with the $watch(). I have the following code:

$scope.$watch('activeFilterAttributes', function() {
    //Code that should update the rows displayed in the table based on the filter
}}

While $scope.activeFilterAttributes is updated properly as and when the user updated the filter in the UI, the $watch is not triggered when this is updated. It is triggered the first time when the application loads but future updates have no effect on this.

I have created a fiddle to demonstrate this: http://jsfiddle.net/nCHQV/

In the fiddle, $scope.info represents the rows of the table, so to speak;
$scope.data represents the filter.
Clicking on the button is equivalent to updating the filter(in the case of the fiddle – the data) and thus updating the rows displayed in the table(in the case of the fiddle – the info). But as can be seen, the info is not updated on clicking the button.

Shouldn’t $scope.$watch be triggered when the array of objects changes?

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

The $watch method takes an optional third parameter called objectEquality that checks that the two objects are equal, rather than just share the same reference. This is not the default behavior because it is a more expensive operation than the reference check. Your watch isn’t being triggered because it still refers to the same object.

See the docs for more info.

$scope.$watch('activeFilterAttributes', function() {
  // ...
}, true); // <-- objectEquality

Add that parameter and all is well.

Method 2

Accepted answer is a bit out of date now as with AngularJS 1.1.4 the $WatchCollection function was added for use with arrays and other collections, which is far less expensive than a $Watch with the deep-equality flag set to true.

So this new function is now preferable in most situations.

See this article for more detailed differences between $watch functions

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply