Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS - rejected $http promise with $routeProvider:resolve

I am trying to use resolve in a $routeProvider to display the new route only when a $http request is finished. If the request is successful, the promise resulting from the $http.post() is resolved and the view is rendered. But if the request fails (timeout or internal error for eg.), the promise is never resolved and the the view is never rendered. How can I deal with request failure using resolve ?

The most important parts of the code is bellow :

app.js

$routeProvider.when('/warrantyResult', {
    templateUrl : 'partials/warranty-result.html',
    controller : 'WarrantyResultCtrl',
    resolve : {
        response : [ 'Warranty', function(Warranty) {
            return Warranty.sendRequest();
        } ]
    }
});

controllers.js

angular.module('adocDemo.controllers', []).controller('HomeCtrl', [ '$scope', function($scope) {

} ]).controller('WarrantyCtrl', [ '$scope', '$http', '$location', 'Warranty', function($scope, $http, $location, Warranty) {
    $scope.submitWarranty = function() {
        $scope.loading = true;
        Warranty.setRequestData($scope.data);
        $location.path('/warrantyResult');
    };
} ]).controller('WarrantyResultCtrl', [ '$scope', 'Warranty', function($scope, Warranty) {

    $scope.request = Warranty.getRequestData();
    $scope.response = Warranty.getResponseData();
} ]);

services.js

angular.module('adocDemo.services', []).factory('Warranty', [ '$http', '$timeout', function($http, $timeout) {
    /**
     * This service is used to query the Warranty Webmethod. The sendRequest
     * method is automaticcaly called when the user is redirected to
     * /warrantyResult route.
     */
    var isDataSet = false;
    var requestData = undefined;
    var responseData = undefined;
    return {
        setRequestData : function(data) {
            //Setting the data
            isDataSet = true;
        },
        getRequestData : function() {
            return requestData;
        },
        sendRequest : function(data) {
            if(isDataSet) {
                var request = $http.post('url/to/webservice', requestData);
                request.success(function(data) {
                    responseData = data;
                });
                return request;
            }   
        },
        getResponseData : function() {
            return responseData;
        }
    };
} ]);

I know i could use a promise around the $http call and resolve it even if the request is a failure, but I'm wondering if there is a simpler solution.

Thanks for reading and, hopefully, helping :)

like image 941
pdegand59 Avatar asked Jun 07 '13 15:06

pdegand59


2 Answers

I think the only way to do it from resolve is to manually resolve the promise returned by Warranty.sendRequest and rewrap it in a new promise:

resolve : {
  response : [ 'Warranty' '$q', function(Warranty, $q) {
    var dfd = $q.defer();

    Warranty.sendRequest().then(function(result) {
      dfd.resolve({ success: true, result : result });
    }, function(error) {
      dfd.resolve({ success : false, reason : error });
    });

    return dfd.promise;

  } ]
}

In WarrantyResultCtrl, you could check if an error occurred and generate a redirect.

EDIT: much cleaner solution:

// WarrantyCtrl
$scope.$on('$routeChangeError', function() {
  // handle the error
});
$scope.submitWarranty = function() {
    $scope.loading = true;
    Warranty.setRequestData($scope.data);
    $location.path('/warrantyResult');
};

(plunker demo)

like image 53
robertklep Avatar answered Oct 04 '22 10:10

robertklep


What I have found is that the controller is not fired at all if the Promise is rejected, and your view is never rendered--same as you.

What I also discovered is that if you handle the Promise with a .then() in your $routeProvider's resolve, the .then() will return a new Promise that is resolved and your controller is fired after all, albeit without the data you are expecting.

For example:

$routeProvider.when('/warrantyResult', {
    templateUrl : 'partials/warranty-result.html',
    controller : 'WarrantyResultCtrl',
    resolve : {
        response : [ 'Warranty', function(Warranty) {
            return Warranty.sendRequest()
                .then(null, function(errorData) {
                    // Log an error here?
                    // Or do something with the error data?
                });
        }]
    }
});

Now in your controller you will want to check whether response is undefined. If it is undefined then you'll know that the call to Warranty.sendRequest() failed, and you can act accordingly.

For what it's worth, I did not go this route. Instead, I injected the $location service into the resolve handler and redirected to an error page if the $http call gets rejected.

UPDATE Just noticed that in your controller you are injecting the Warranty service when you should instead be injecting the response that you defined in your resolve. That will prevent your view from rendering until the Promise returned from Warranty.sendRequest() is resolved.

like image 43
Steve J Avatar answered Oct 04 '22 10:10

Steve J