106

I have a single factory defined with ngResource:

App.factory('Account', function($resource) {
    return $resource('url', {}, {
        query: { method: 'GET' }
    });
});

I am making multiple calls to the query method defined on this factory. The calls can happen asynchronously, but I need to wait for both calls to complete before continuing:

App.controller('AccountsCtrl', function ($scope, Account) {
    $scope.loadAccounts = function () {
        var billingAccounts = Account.query({ type: 'billing' });
        var shippingAccounts = Account.query({ type: 'shipping' });

        // wait for both calls to complete before returning
    };
});

Is there a way to do this with AngularJS factories defined with ngResource, similar to jQuery's $.when().then() functionality? I would prefer not to add jQuery to my current project.

Nathan Donze
  • 1,394
  • 2
  • 9
  • 9

3 Answers3

205

You'll want to use promises and $q.all().

Basically, you can use it to wrap all of your $resource or $http calls because they return promises.

function doQuery(type) {
   var d = $q.defer();
   var result = Account.query({ type: type }, function() {
        d.resolve(result);
   });
   return d.promise;
}

$q.all([
   doQuery('billing'),
   doQuery('shipping')
]).then(function(data) {
   var billingAccounts = data[0];
   var shippingAccounts = data[1];

   //TODO: something...
});
Ben Lesh
  • 107,825
  • 47
  • 247
  • 232
  • 17
    Resources don't return promises, they return objects to be filled in the future. However in the *unstable* 1.1.3 version, resources also have `$then` property but do not expose any promise object. Exposing `$promise` completely would be in 1.1.4 – Umur Kontacı Mar 08 '13 at 20:46
  • @UmurKontacı This is unfortunately *not* in angular 1.1.4! – nh2 Apr 30 '13 at 08:59
  • Details about the *resources are not promises problem* can be found in [this thread](https://groups.google.com/forum/#!msg/angular/N5yBJvl7Pbg/C48oelQu6DgJ) and in [this pull request](https://github.com/angular/angular.js/pull/2060#issuecomment-17026079). – nh2 Apr 30 '13 at 09:01
  • 1
    [This answer](http://stackoverflow.com/a/16076875/263061) shows how to write it once that is implemented. – nh2 Apr 30 '13 at 09:19
  • It might be a bit cleaner to get `result` from the callback than from the assignment: `Account.query... function(result) { ...` instead of `var result = Account.query ... function() { ...`. – nh2 Apr 30 '13 at 11:29
  • @nh2 ... you could do that of course! It's personal preference. I was just following the pattern set by the Angular team's examples. It's the same reference either way. – Ben Lesh May 01 '13 at 12:36
  • 3
    Your answer is very helpful and I believe it is the most sensible way to convert resources to promises in the current angular. It might be helpful to add that in the documentation of `$q`, which you linked to, it guarantees that the result array is in the same order as the promise array. – nh2 May 04 '13 at 05:18
  • This was helpful especially the latest comment about guaranteeing the result array is in the same order as the promise array. – rschlachter Jun 12 '13 at 18:48
21

I think a better solution is:

$q.all([
   Account.query({ type: 'billing' }).$promise,
   Account.query({ type: 'shipping' }).$promise
]).then(function(data) {
   var billingAccounts = data[0];
   var shippingAccounts = data[1];

   //TODO: something...
});
  • 1
    For me worked without $promise at the end... Just like: Account.query({ type: 'billing' }), Account.query({ type: 'shipping' }) – georgeos Mar 17 '17 at 22:50
13

The solution from Ben Lesh is the best but it's not complete. If you need to handle error conditions--and, yes, you do--then you must use the catch method on the promise API like this:

$q.all([
   doQuery('billing'),
   doQuery('shipping')
]).then(function(data) {
   var billingAccounts = data[0];
   var shippingAccounts = data[1];

   //TODO: something...

}).catch(function(data) {

   //TODO: handle the error conditions...

}).finally(function () {

  //TODO: do final clean up work, etc...

});

If you don't define catch and all of your promises fail, then the then method won't ever execute and thus will probably leave your interface in a bad state.

Nick A. Watts
  • 819
  • 10
  • 16