Canceling $http Requests in AngularJS

Thursday, April 24, 2014

One of the objects you can pass along in the config argument of an $http operation is a timeout promise. If the promise resolves, Angular will cancel the corresponding HTTP request.

Sounds easy, but in practice there are a few complications. Before we get to the complications, let’s look at some easy code. Imagine the following  inside of a controller where a user can click a Cancel button.

var canceller = $q.defer();

$http.get("/api/movies/slow/2", { timeout: canceller.promise })
     .then(function(response){
        $scope.movie = response.data;
    });

$scope.cancel = function(){
    canceller.resolve("user cancelled");  
};

The code passes the canceller promise as the timeout option in the config object. If the user clicks cancel before the request completes, we’ll see the cancellation in the Network tab of the developer tools.

Cancelled HTTP Request

The complications come in real life scenarios where we have to manage multiple requests, provide the ability to cancel an operation to other client components, and figuring out if a given request is cancelled or not.

First, let’s look at a service that wraps $http to provide domain oriented operations. Typically services that talk using $http return simple promises, but now we need to return objects that provide a promise for the outstanding request, and  a method that can cancel the request.

app.factory("movies", function($http, $q){

    var getById = function(id){
        var canceller = $q.defer();

        var cancel = function(reason){
            canceller.resolve(reason);
        };

        var promise =
            $http.get("/api/movies/slow/" + id, { timeout: canceller.promise})
                .then(function(response){
                   return response.data;
                });

        return {
            promise: promise,
            cancel: cancel
        };
    };

    return {
        getById: getById
    };

});

A client of the service might need to track multiple requests, if there is a UI like the following that allows a user to send multiple requests.

<div ng-controller="mainController">

    <button ng-click="start()">
        Start Request
    </button>

    <ul>
        <li ng-repeat="request in requests">
            <button ng-click="cancel(request)">Cancel</button>
        </li>
    </ul>

    <ul>
        <li ng-repeat="m in movies">{{m.title}}</li>
    </ul>

</div>

The following code will manage the UI and allow the user to cancel any outstanding request.

app.controller("mainController", function($scope, movies) {

    $scope.movies = [];
    $scope.requests = [];
    $scope.id = 1;

    $scope.start = function(){

        var request = movies.getById($scope.id++);
        $scope.requests.push(request);
        request.promise.then(function(movie){
            $scope.movies.push(movie);
            clearRequest(request);
        }, function(reason){
            console.log(reason);
        });
    };

    $scope.cancel = function(request){
        request.cancel("User cancelled");
        clearRequest()
    };

    var clearRequest = function(request){
        $scope.requests.splice($scope.requests.indexOf(request), 1);
    };
});

The logic gets messy and could use some additional encapsulation to keep request management from overwhelming  the controller, but this is the essence of what you’d need to do to allow cancellation of $http operations.


Comments
gravatar nikolaj Ivancic Friday, April 25, 2014
As most of your blogs posts have closed the comments section, I am barging in here, out of context, to tell you that your AngularJS abstractions articles are simply phenomenal. I read everything about Angular I could find - including likely all relevant Pluralsight course and was still left with the vague feeling that I do not have the completely clear overall picture. Only now, reading what you wrote in your blogs, I finally am getting it - and it is your sensitivity about issues that may be confusing and then taking care to address them in a very clear fashion that is missing everywhere else. You really made me happy, Scott -- thank you!
gravatar scott Saturday, April 26, 2014
Thank you very much!
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!