Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

angularjs

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?

like image 832
user109187 Avatar asked Mar 30 '13 17:03

user109187


2 Answers

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.

like image 171
Josh David Miller Avatar answered Nov 10 '22 07:11

Josh David Miller


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

like image 43
Amicable Avatar answered Nov 10 '22 08:11

Amicable