8

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?

1
  • 2
    Just keep chaining .then. Each call to .details should be changed to the original .then. Commented Dec 19, 2014 at 17:19

3 Answers 3

13

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);        
        });
    }
});
Sign up to request clarification or add additional context in comments.

Comments

1

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

Comments

1

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);
    });
}]);

4 Comments

Will this call the totalling function once when the final details call completes or will it call it every time one of the details calls returns?
the final .then will happen once.
I've just tried this and it doesn't work because the else portion inside the loop has i as a captured variable. By the time it executes, i is greater than the length of resources so you get an exception.
oops, at least it's an easy fix. dryer too.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.