Sohail Faruqui
Sohail Faruqui

Reputation: 452

Resolve multiple promises in route's resolve before initializing controller

I have 2 http calls needed to be reslove before initializing my controller, currenly using Angular UI router and I have resolve map under $stateProvider.state('stateName', {stateObject}) my stateObject as bellow

    $stateProvider.state('stateName', {
        url  : '/myURL',
        params: {
                 data1: undefined,
                 data2 : undefined
            },
        resolve: {
            dataTobeResolve : function($stateParams,$q) {

                var deferred = $q.defer();
                var deferredObj = {};
                deferredObj.d1 = $q.defer();
                deferredObj.d2 = $q.defer();

                var result = {
                    data1: {},
                    data2: {}
                }

                if(angular.isDefined($stateParams.data1)) {
                    result.data1 = $stateParams.data1;
                    deferredObj.d1.resolve();
                }
                else {
                    httpCall().then(function(response) {
                        AsyncMethodCall(response.data).then(function(resolvedData) {
                            result.data1 = resolvedData;
                            deferredObj.d1.resolve();       
                        });
                    });
                }

                if(angular.isDefined($stateParams.data2)) {
                    result.data2 = $stateParams.data2;
                    deferredObj.d2.resolve();
                }
                else {
                    AsyncMethodCall().then(function(resolvedData) {
                        result.data2 = resolvedData;
                        deferredObj.d2.resolve();       
                    });
                }

                $q.all(deferredObj).then(function() {
                    deferred.resolve(result);
                });
                return deferred.promise;
            }
        }
    });

how ever despite of deferredObj.d1 being resolve controll goes to then(function(){}) of $q.all(deferredObj) which is unexpected behaviour and I belive that all of the promise maps of deferredObj should be resolve before this line get executed, my controller get initialize despite of one of the promises being resolved

Upvotes: 0

Views: 1150

Answers (2)

Sohail Faruqui
Sohail Faruqui

Reputation: 452

I have found the issue and it is due to passing deferred object into $q.all() rather than hash/array of promises. so following code will solve the problem

$stateProvider.state('stateName', {
    url  : '/myURL',
    params: {
             data1: undefined,
             data2 : undefined
        },
    resolve: {
        dataTobeResolve : function($stateParams,$q) {

            var deferred = $q.defer();
            var promises = {};
            var d1 = $q.defer();
            var d2 = $q.defer();
            promises.d1 = d1.promise;
            promises.d2 = d2.promise;

            var result = {
                data1: {},
                data2: {}
            }

            if(angular.isDefined($stateParams.data1)) {
                result.data1 = $stateParams.data1;
                d1.resolve();
            }
            else {
                httpCall().then(function(response) {
                    AsyncMethodCall(response.data).then(function(resolvedData) {
                        result.data1 = resolvedData;
                        d1.resolve();       
                    });
                });
            }

            if(angular.isDefined($stateParams.data2)) {
                result.data2 = $stateParams.data2;
                d2.resolve();
            }
            else {
                AsyncMethodCall().then(function(resolvedData) {
                    result.data2 = resolvedData;
                    d2.resolve();       
                });
            }

            $q.all(promises).then(function() {
                deferred.resolve(result);
            });
            return deferred.promise;
        }
    }
});

but still I appreciate if someone provide better solution

Upvotes: 0

georgeawg
georgeawg

Reputation: 48968

Since $q.all, $http, and the .then method all returns promises, the is no need to manufacture a promise with $q.defer. Only use $q.defer to make promises from old style callback-only asynchronous APIs.

Use $q.when to make a promise from a synchronous source or from a promise from outside the AngularJS framework.

resolve: {
    dataTobeResolve : function($stateParams,$q) {

        var d1Promise;
        var d2Promise;

        if(angular.isDefined($stateParams.data1)) {
            d1Promise = $q.when($stateParams.data1);
        }
        else {
            d1Promise = httpCall()
              .then(function(response) {
                   return response.data;
            });
        }

        if(angular.isDefined($stateParams.data2)) {
            d2Promise = $q.when($stateParams.data2)
        }
        else {
            d2Promise = httpCall2()
              .then(function(response) {
                  return response.data;      
            });
        }

        return $q.all([d1Promise, d2Promise]);
    }
}

In the above example, two promises are created by either $q.when of a parameter or derived from a promise-based asynchronous API. A composite promise is created from the two promises with the $q.all method.

Using derived promises has the advantage that rejections are automatically carried forward to the final promise. As currently written, the $q.defer method, will hang the state change if either of the XHR calls has an error from the server.

Upvotes: 1

Related Questions