Reputation: 3562
I have a REST API that I want to call from an AngularJS service like this:
angular.module('myModule').service('MyApi', ['$http', function($http) {
return ({
resources: resources,
details: details
});
function resources() {
return $http.jsonp('/api/resources');
}
function details(key) {
return $http.jsonp('/api/details/' + id);
}
}]);
There are other implementation details removed there such as authentication that aren't important. The API is provided by a third party so I can't change it.
GET /api/resources
returns something like:
[{ "key": "one" }, { "key": "two" }]
GET /api/details/one
returns something like:
{ "count": 5 }
I then have a controller where I want to call MyApi.resources()
, wait for the results and then for each result, call MyApi.details(resource)
. When the final call to MyApi.details(resource)
completes, I want to run a function to aggregate some results from the set of details, but I can't work out how to trigger this at the end.
My controller currently looks like this:
angular.module('myModule').controller('MyCtrl', ['$scope', 'MyApi', function($scope, MyApi) {
$scope.results = new Array();
MyApi.resources().then(function(response) {
var resources = response.data;
var length = resources.length;
for (var i = 0; i < length; i++) {
MyApi.details(resources[i].key).then(function(response) {
$scope.results.push(response.data.count);
});
}
});
// how do I get this line to run only after all the results are returned?
$scope.total = $scope.results.reduce(function(a, b) { return a + b; }, 0);
}]);
What is the best way to achieve the aggregation at the end?
Upvotes: 8
Views: 6866
Reputation: 95030
Inside your first .then, create a promise and chain all of the requests in the loop off of it, then return it. Then, you can use .then to run code when it is finished.
angular.module('myModule').controller('MyCtrl', ['$scope', 'MyApi', function($scope, MyApi) {
$scope.results = new Array();
MyApi.resources().then(function(response) {
var resources = response.data;
var length = resources.length;
var promise;
function getDetails(key) {
return function () {
MyApi.details(key).then(function(response) {
$scope.results.push(response.data.count);
})
};
}
for (var i = 0; i < length; i++) {
if (i === 0) {
promise = getDetails(resources[i].key)();
} else {
promise.then(getDetails(resources[i].key));
}
}
return promise;
}).then(function () {
$scope.total = $scope.results.reduce(function(a, b) { return a + b; }, 0);
});
}]);
Upvotes: 1
Reputation: 1704
You can use deferred's function $q.all.
angular.module('myModule').controller('MyCtrl', ['$scope', 'MyApi', '$q', function($scope, MyApi, $q) {
$scope.results = new Array();
MyApi.resources().then(function(response) {
var resources = response.data;
var length = resources.length;
var defer = $q.defer();
var promises = [];
angular.forEach(resources, function(value) {
promises.push(MyApi.details(resources[i].key));
});
$q.all(promises).then(function() {
$scope.total = $scope.results.reduce(function(a, b) { return a + b; }, 0);
});
}
});
Upvotes: 13
Reputation: 2802
Two ways:
$q service
Use $q.all() to AND all your details promises
promise chain
call next details only if the previous one is resolved
Upvotes: 1