2

I'm new to AngularJS, so this might actually point to some core concept that I don't yet understand completely. I'm trying to treat "remote data" like local data using $q & promise objects. Till the time the remote data is not fetched, the promise object doesn't resolve, but as soon as it resolves, I want all the dependent data-bindings in the view to get updated. However, the following approach is resulting in an infinite loop where remote_total is being called repeatedly, even through the previous call to remote_total resulted in a resolved promise object.

Here's what my view looks like

<div ng-controller="MyController">
  {{ remote_total() }}
</div>

Here's the relevant snippet from the controller:

function MyController($scope, $q, $http) {
  $scope.remote_total = function() {
    var def = $q.defer();
    $http.get('/my/remote/service.json').success(function(data) {
      def.resolve(data);
    });
    return def.promise;
  }
}

Firstly, it would be great if anyone could explain to me why exactly is this getting into an infinite loop. Secondly, what's the best way to achieve what I'm trying to do?

Saurabh Nanda
  • 6,373
  • 5
  • 31
  • 60

2 Answers2

5

NOTE: See UPDATE below for Angular 1.2+

Actually interesting thing with AngularJS's promises (that $q provides) that they're recognized by AngularJS. And another that they are chainable!

So in your code you can just do

$scope.remote_total = $http.get('/my/remote/service.json').then(function(data) {
  return data;
});

and at view just <div>{{remote total}}</div> (note: not function, just value).

And AngularJS will automatically recognize it's promise, resolve $http.get promise, then chain into your function and put resulting value into template.

That's all :)

UPDATE: Automatic de-wrapping of promises are disabled by default in AngularJS 1.2 and would be completely removed in 1.3.

Code that will work:

$http.get('/my/remote/service.json').then(function(response) {
  $scope.remote_total = response.data;
});
georgeawg
  • 48,608
  • 13
  • 72
  • 95
Valentyn Shybanov
  • 19,331
  • 7
  • 66
  • 59
3

Angular does dirty checking to achieve 2 way binding. In each cycle, it stores the previous value of property being watched and compares it with the new value. If that property is a function, it is called and the result is being compared.

You did put a function to be watched and in each cycle, your function is being called, hence resulting in http request.

What you want to do is to create the promise on its own and attach that promise to scope for watching (or showing in view).

Also, $http service already returns a promise, you don't need to create another promise.

function MyController($scope, $q, $http) {
  $scope.getRemoteTotal = function() {
    var def = $http.get('/my/remote/service.json').then(function(response) {
      $scope.remoteTotal = response.data;
    });
  }
  $scope.getRemoteTotal();
}

.

<div ng-controller="MyController">
  {{ remoteTotal }}
</div>
georgeawg
  • 48,608
  • 13
  • 72
  • 95
Umur Kontacı
  • 35,403
  • 8
  • 73
  • 96
  • So, if I understand this correctly, `{{ remote_total }}` sets up a watch on the function, which would mean that angular would call that function each digest-cycle (?) to figure out if it has changed or not. And each time it's called, it would end up making the HTTP request. Correct? – Saurabh Nanda Jan 22 '13 at 06:45
  • yes, if the item being watched is a function, it is called in each cycle. here misko's (one of the core developers) explanation on angular's databinding http://stackoverflow.com/questions/9682092/databinding-in-angularjs – Umur Kontacı Jan 22 '13 at 07:04
  • 3
    this is very esoteric - even unintuitive - behaviour from angular – Paul Taylor Jul 04 '13 at 10:33