21

How does one cancel an ongoing Angular $http request when there's a new request?

I've got an Angular app with a view that updates live as the user types. Sometimes old requests complete after the latest request, meaning the view displays the wrong data. What's the most straightforward way to cancel the previous request when there's a new one?

Using Angular 1.5, for what it's worth.

<input ng-model = "query" ng-keyup = "search()"/>
{{results | json}}

// In the controller:
    $scope.search = function(){
        $http({
            method: "GET",
            url: "/endpoint.php"
            params: {
                query: $scope.query
            }
        }).then(function(response){
            $scope.results = response.data;
        })
    }

One solution I have tried:

// In the controller:
    var canceler = $q.resolve();    // New
    $scope.search = function(){
        canceler.resolve("Canceling old request");     // New
        $http({
            method: "GET",
            url: "/endpoint.php"
            params: {
                query: $scope.query
            },
            timeout: canceler.promise    // New
        }).then(function(response){
            $scope.results = response.data;
        })
    }

In this scenario, even though I'm calling canceler.resolve before the $http request, the request turns up as "failed".

Any insights?

edit: Solution found!

// In the controller:
    var canceler = $q.defer();

    $scope.search = function(){
        canceler.resolve("cancelled"); // Resolve the previous canceler
        canceler = $q.defer();        // Important: Create a new canceler! 
                                      // Otherwise all $http requests made will fail
        $http({
            method: "GET",
            url: "/endpoint.php"
            params: {
                query: $scope.query
            }
        }).then(function(response){
            $scope.results = response.data;
        })
    }
caitlin
  • 2,769
  • 4
  • 29
  • 65

2 Answers2

12

When you start a new search, call the cancel() function. And you can use a resolved variable to make sure that you do not abort your $http call before it starts. Something like this:

var canceler = $q.defer();
var resolved = false;

var cancel = function() {
    canceler.resolve("http call aborted");
};

$scope.search = function() {
    if (resolved) {
        cancel();
    }

    canceler = $q.defer();
    resolved = true;

    $http({
        method: "GET",
        url: "/endpoint.php"
        params: {
            query: $scope.query
        }
        timeout: canceler.promise
    }).then(function(response) {
        $scope.results = response.data;
        resolved = false;
    })
}

Don't forget to inject $q in your controller/directive/service.

6324
  • 4,678
  • 8
  • 34
  • 63
  • Hmm - I try this- `ng-click = "cancel(); search();"` and I still get the same XHR failed in my console. I'm injecting $q, too. – caitlin Feb 13 '16 at 02:03
  • @schnauss are you using var canceler = $q.resolve() or var canceler = $q.defer();? – 6324 Feb 13 '16 at 02:05
  • `var canceler = $q.defer();`. I'm stumped. – caitlin Feb 13 '16 at 02:06
  • According to your code, the canceler is already resolved before you do http call. – 6324 Feb 13 '16 at 02:09
  • Hm. Wouldn't I want to do that in order to cancel the previous request, if any? – caitlin Feb 13 '16 at 02:13
  • @schnauss can you try my answer again, i just add a "resolved" variable to make sure that you dont abort the http call before it starts. I think this time it should work. Please make sure you have the right logic of when to set resolved=true in your code. – 6324 Feb 13 '16 at 02:21
  • No luck, I still get the "XHR failed loading" error. – caitlin Feb 13 '16 at 02:29
  • 2
    I found the solution: I had to create a new canceler with each search request, otherwise it would always fail. – caitlin Feb 13 '16 at 02:46
10

The solution is to set up the promise, then with each search() cancel it and reinitialize a new one. This will reliably cancel any previous $http():

var canceler = $q.defer();

$scope.search = function() {

    canceler.resolve();

    canceler = $q.defer();

    $http({
        method: "GET",
        url: "/endpoint.php"
        params: {
            query: $scope.query
        }
        timeout: canceler.promise
    }).then(function(response) {
        $scope.results = response.data;
    })
}
caitlin
  • 2,769
  • 4
  • 29
  • 65