Reputation: 1792
I am trying to setup a watch on the return value of an asynchronous function, but keep running into infinite digest loops. Here's my $watch
$scope.$watch(function(){
var friendsCount = PublicUserData().then(function(data){
console.log(data.length);
return data.length;
});
return friendsCount;
}, function(newValue, oldValue) {
console.log("change"+ newValue);
});
I can see in the chrome console that both console.log
get called, but the second (on the callback) gets called far more often than the first.
PublicUserData
uses $q
:
.factory('PublicUserData', function($http, $q){
return function(){
var defer = $q.defer();
$http.get('/api/v1/users/').then(function(data){
defer.resolve(data.data.users);
})
return defer.promise;
}
})
I've tried a few things, like setting the watch expression as a $scope outside of my $watch
, but that ends up returning the factory's code, rather than the factory's return value.
Is it possible to use $watch
with a function that implements a promise? Or should I be using something else?
Upvotes: 1
Views: 7143
Reputation: 18339
Just to offer another similar solution to ivarni's excellent answer. If you want to poll (whether using timeout or interval) you can do this in a factory and just use a simple scope variable:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, PublicUserData) {
$scope.pollData = PublicUserData.pollData;
});
app.factory('PublicUserData', function($http, $q, $interval){
var srv = {
pollFn: function() {
srv.stopInterval = $interval(function() {
srv.pollData.friendCount++;
console.log('polled');
}, 1000);
},
pollData: {friendCount: 0, other: {}},
stop: function() {
$interval.cancel(srv.stopInterval);
}
}
srv.pollFn();
return srv;
});
Demo: http://plnkr.co/edit/0467sypyeg0Wpdr4302k?p=preview
Upvotes: 2
Reputation: 8465
If you already have a promise why do you do $watch?
PublicUserData().then(function(data){
$scope.friendsCount = data
});
if you need to run it in interval best performance gives a $timeout
callForFriendsCount = function () {
PublicUserData().then(function(data){
$scope.friendsCount = data
$timeout(callForFriendsCount, 1000)
});
}
$timeout is better idea over an interval because if in any case older call will take longer than 1000 ms it will overlap the newest call, with $timeout you will avoid that
Upvotes: 2
Reputation: 17878
You really really really really don't want to be doing AJAX requests from a $watch
. Really.
Angular uses dirty-checking and your $watch
function will be invoked repeatedly every single time anything on the $scope
changes. If you could get it to work it would completely kill the performance of your application while spamming the server.
It looks like what you're trying to do is to have a counter or something that shows how many friends are logged in? That problem is better solved with either server events or a polling mechanism, polling being the easiest to pull off.
Consider something like this instead:
$interval(function() {
PublicUserData().then(function(data) {
$scope.data.friendsCount = data.length;
});
}, 1000);
That will poll the server every second (which is probably overkill, but far less frequent than trying to do it in a $watch
) and update the $scope
with the correct value.
Upvotes: 6