0

I have two controllers and a factory. I am injecting the factory and a controller into a main controller, here are the code snippets below:

Factory:

app.factory('TallyFactory', function(){ return { correct: 0, incorrect: 0 }; }); 

TallyController that accepts the TallyFactory:

app.controller('TallyController', function($scope, TallyFactory){ $scope.$watch('scores', function(newValue, oldValue){ console.info('changed!'); console.log('Old: ' + JSON.stringify(oldValue)); console.log('New: ' + JSON.stringify(newValue)); }); $scope.scores = TallyFactory; }); 

MainController which accepts the TallyFactory and changes the value of the TallyFactory Object:

app.controller('MainController', function ($scope,TallyFactory) { $scope.addTally = function(){ TallyFactory.correct++; } }); 

Notice in my TallyController (second snippet), I added a watcher to the $scope, listening for when "scores" changes.

When I run the "addTally" function from my MainController with an ng-click, it changes the value of my Factory Object (TallyFactory). Since there was a change, and I specifically created a watcher on my TallyController ($scope.scores), shouldn't the digest cycle be triggered and my console update with the new value of my TallyFactory?

Also, my TallyController is NOT nested in my MainController, they are separate from each other (not 100% sure if that matters).

I'm not sure why the Digest Loop isn't triggered since the TallyFactory changed values.

5
  • should work even without the $watch. Create a demo that replicates problem Commented Aug 19, 2015 at 23:22
  • You're watching the wrong thing. $scope.$watch('scores.correct', ... Commented Aug 19, 2015 at 23:24
  • do you have both controllers loaded on the same page? because how does your app load the TallyController? Commented Aug 19, 2015 at 23:27
  • 1
    Just so it's clear what I meant, when you say that "it changes the value of my Factory Object (TallyFactory)" that's not actually correct. It changes a property of TallyFactory, but TallyFactory itself remains unchanged. For a deep watch, add true as the 3rd argument on the watch. $scope.$watch('scores', function(){}, true) Commented Aug 19, 2015 at 23:31
  • 1
    @Claies The comment is misleading, it is the correct use for factory (though value is shorter). The object returned by this factory is the same every time the service is injected. It won't be a singleton otherwise. Commented Aug 20, 2015 at 1:30

2 Answers 2

2
$scope.$watch('scores.correct', function(newValue, oldValue){ ... }); 

to watch for particular property. And

$scope.$watchCollection('scores', function(newValue, oldValue){ ... }); 

to watch for all of them.

Consider using deep $watch

$scope.$watch('scores', function(newValue, oldValue){ ... }, true); 

instead if scores properties may contain objects.

In comparison with deep $watch, $watchCollection provides significant performance boost even for simple shallow objects. Here is profiler timeline for $watchCollection:

$watchCollection profiler timeline

While deep $watch profiler timeline will look like this:

deep $watch profiler timeline

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

5 Comments

scores is just pointing to an object here so using the deep equality check is what fixes the problem. The issue is the factory object itself isn't changing only the properties within it, but like you said if it were an array the watchCollection makes sense, and adding the third param for deep equality check true fixes the issue.
@shaunhusain $watchCollection is the use case for arrays and shallow objects (and scores is shallow object).
oh huh well that's interesting just checked out the docs you are correct, I wonder if there is any actual performance difference in the watchCollection vs a deepEquality check in a regular watch for a shallow object.
@shaunhusain I've added profiler data to the answer. Yes, there is apparent performance difference.
Interesting stuff thanks for sharing the profiling results
1

I had some doubts about the exact correctness of the answer but after copying your code into a snippet that can be run I just verified here that estus' answer is acceptable.

angular.module('app', []) .controller('TallyController', function($scope, TallyFactory){ $scope.scores = TallyFactory; $scope.$watch('scores', function(newValue, oldValue){ alert('changed!'); console.log('Old: ' + JSON.stringify(oldValue)); console.log('New: ' + JSON.stringify(newValue)); }, true); }) .factory('TallyFactory', function(){ return { correct: 0, incorrect: 0 }; }) .controller('MainController', function ($scope,TallyFactory) { $scope.addTally = function(){ TallyFactory.correct++; } });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script> <div ng-app='app'> <div ng-controller="TallyController"> {{scores}} </div> <div ng-controller="MainController"> <button ng-click="addTally()">Add Tally</button> </div> </div>

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.