Adam Rodger
Adam Rodger

Reputation: 3562

AngularJS Function After Multiple Async Service Calls

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

Answers (3)

Kevin B
Kevin B

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

Josef Joe Samanek
Josef Joe Samanek

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

hjl
hjl

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

Related Questions