$watch not being triggered on array change

AngularjsAngularjs Scope

Angularjs Problem Overview


I'm trying to figure out why my $watch isn't being triggered. This is a snippet from the relevant controller:

$scope.$watch('tasks', function (newValue, oldValue) {
    //do some stuff
    //only enters here once
    //newValue and oldValue are equal at that point
});

$scope.tasks = tasksService.tasks();

$scope.addTask = function (taskCreationString) {
    tasksService.addTask(taskCreationString);//modifies tasks array
};

On my view, tasks is clearly being updated correctly as I have its length bound like so:

<span>There are {{tasks.length}} total tasks</span>

What am I missing?

Angularjs Solutions


Solution 1 - Angularjs

Try $watch('tasks.length', ...) or $watch('tasks', function(...) { ... }, true).

By default, $watch does not check for object equality, but just for reference. So, $watch('tasks', ...) will always simply return the same array reference, which isn't changing.

Update: Angular v1.1.4 adds a $watchCollection() method to handle this case:

> Shallow watches the properties of an object and fires whenever any of the properties change (for arrays this implies watching the array items, for object maps this implies watching the properties). If a change is detected the listener callback is fired.

Solution 2 - Angularjs

Very good answer by @Mark. In addition to his answer, there is one important functionality of $watch function you should be aware of.

With the $watch function declaration as follows:

$watch(watch_expression, listener, objectEquality)

The $watch listener function is called only when the value from the current watch expression (in your case it is 'tasks') and the previous call to watch expression are not equal. Angular saves the value of the object for later comparison. Because of that, watching complex options will have disadvantageous memory and performance implications. Basically the simpler watch expression value the better.

Solution 3 - Angularjs

I would recommend trying

$scope.$watch('tasks | json', ...)

That will catch all changes to the tasks array, as it compares the serialized array as a string.

Solution 4 - Angularjs

For one dimensional arrays you may use $watchCollection

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

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionkmkempView Question on Stackoverflow
Solution 1 - AngularjsMark RajcokView Answer on Stackoverflow
Solution 2 - AngularjsTomView Answer on Stackoverflow
Solution 3 - AngularjsBrian P JohnsonView Answer on Stackoverflow
Solution 4 - AngularjsKarl AdlerView Answer on Stackoverflow