18

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?

2 Answers 2

66

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.

Sign up to request clarification or add additional context in comments.

24 Comments

Thank you Josh - I can't remember the number of times you have provided an awesome explanation to the cause of the issue and a solution. Thank you very much. By the way - I am testing another of your awesome solutions for the question provided in stackoverflow.com/questions/15316363/… - as of now it does what I expect it to and will soon mark it as the correct answer after some more tests.
@JoshDavidMiller, Thanks for the nice explanation. As you said its an expensive operation, could you please let us know how to avoid getting to a situation of using that 3rd parameter.
@rajkamal I wouldn't say it's "expensive" per se but it is more expensive than the reference check. It's not always avoidable though. But I would definitely try to avoid doing it with large objects because the larger the object, the longer the check takes. Other than that, I'm not sure there are any good general rules. It's probably one of things to sort out when benchmarking your code. If the digests are taking too long, maybe the model should be reworked a little bit. Sorry I don't have a better answer here. Perhaps someone else will share her wisdom.
Heh, I simplified it to a checkbox, but my implementation is exactly that, a sliding button using an ios-style btn-switch. But, I see what you mean about it being Angular-ish. I can do either implementation, and the controller and model are unchanged, only my template changes.. Thanks, @JoshDavidMiller.
Touche. Thanks again for the help. Angular is pretty damn cool, the 2-way binding and clean separation are amazing. But hell of a learning curve!
|
5

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.