user2936314
user2936314

Reputation: 1792

angular $scope.$watch with an function that returns a promise

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

Answers (3)

lucuma
lucuma

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

maurycy
maurycy

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

ivarni
ivarni

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

Related Questions