14

I'm still wrapping my brain around Angular.JS.

I have two independent $http calls that retrieve data from remote web services. I have an action that I want to fire off after both service calls have been completed.

  1. The first service call will populate the $scope.model
  2. The second service call, modifies data in the $scope.model (it adds some counter properties that are rendered in the view)

Another unique requirement is that eventually the second service call will be called and updated outside the controller with the $scope.model. It's a notification message pump.

I'm guessing I'm going to use promises $q and possibly $service, but I'm not really sure where to start for something like this following some best practices.

I know it doesn't sound like async calls are appropriate here, since my example it could be simplified by doing it syncronously. However, the second service call is a notification updater, so it'll get continually polled to the server (eventually a websocket will be used).

It's a common pattern I'll see in this application.

taudep
  • 2,871
  • 5
  • 27
  • 36

2 Answers2

29

You'll want to use $q promises. Specifically $q.all(). All $http methods will return promises. $q.all([promise, promise, promise]).then(doSomething) will wait for all promises to resolve then call doSomething passing an array of the promises results to it.

app.service('myService', ['$http', '$q', function($http, $q) {
   return {
      waitForBoth: function() { 
          return $q.all([
             $http.get('/One/Thing'),
             $http.get('/Other/Thing')
          ]);
      };
   }
}]);

Then call it:

app.controller('MyCtrl', ['$scope', 'myService', function($scope, myService) {
   myService.waitForBoth().then(function (returnValues){
       var from1 = returnValues[0].data;
       var from2 = returnValues[1].data;
       //do something here.
   });
}]);

Here's a demonstration Plunker for you.

SoluableNonagon
  • 11,541
  • 11
  • 53
  • 98
Ben Lesh
  • 107,825
  • 47
  • 247
  • 232
  • Went with this version of the answer initially. In the future, I might change my code to watch the property for changes, since it'll eventually be listening to a message pump. And bonus points for using the minifiable service declaration syntax in the example code. – taudep Mar 01 '13 at 17:06
  • Haha... I see no bonus points! :P I changed the controller declaration to have the minify-friendly syntax as well. Glad I could help. – Ben Lesh Mar 01 '13 at 19:29
  • Thank you for writing out that you need to pass an array of values.....argg I missed that for a little while! – Kelly Milligan Aug 16 '13 at 15:40
  • but What about failures? if any of the promises we're waiting on fails, the entire batch fails. so i think @MarkRajcok trick is better – Mojtaba Pourmirzaei Apr 21 '15 at 03:54
  • 1
    @MojtabaPourmirzaei, that was the point of the OP's question. But if you care about individual success or failure, then you handle the promises individually, then use all() to signal that they all completed successfully. Something like `$q.all([promiseA.then(successA).catch(failA), promiseB.then(successB).catch(failB)]).then(allSuccess).catch(atLeastOneFailed)` – Ben Lesh Apr 23 '15 at 19:32
2

Create a service for your first service call. Pete's answer will help with that: https://stackoverflow.com/a/12513509/215945

In your controller, in the then() callback, add a $watch for the appropriate $scope.model property:

app.controller('MainCtrl', function(myService, $scope) {
  myService.async().then(function(myData) {
    $scope.model = myData;
    $scope.$watch('model.???', function(newVal, oldVal) {
      if(newVal !== oldVal) {
         // do something here, now that $scope.model.??? has changed
      }
    })
  });
});
Community
  • 1
  • 1
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • 1
    Looks like it's that time of day where I trade answers with @MarkRajcok – Ben Lesh Feb 27 '13 at 17:03
  • Playing with both solutions. Thanks! I'll mark one as complete soon. I'd like to render the data in the first call before the second call has necessarily completed, so $watch on the model is likely key for me over the waitForBoth idea below. – taudep Feb 28 '13 at 14:51