AngularJS abort all pending $http requests on route change
Solution 1:
I put together some conceptual code for this. It might need tweaking to fit your needs. There's a pendingRequests
service that has an API for adding, getting and cancelling requests, a httpService
that wraps $http
and makes sure all requests are tracked.
By leveraging the $http
config object (docs) we can get a way to cancel a pending request.
I've made a plnkr, but you're going to need quick fingers to see requests getting cancelled since the test-site I found typically responds within half a second, but you will see in the devtools network tab that requests do get cancelled. In your case, you would obviously trigger the cancelAll()
call on the appropriate events from $routeProvider
.
The controller is just there to demonstrate the concept.
DEMO
angular.module('app', [])
// This service keeps track of pending requests
.service('pendingRequests', function() {
var pending = [];
this.get = function() {
return pending;
};
this.add = function(request) {
pending.push(request);
};
this.remove = function(request) {
pending = _.filter(pending, function(p) {
return p.url !== request;
});
};
this.cancelAll = function() {
angular.forEach(pending, function(p) {
p.canceller.resolve();
});
pending.length = 0;
};
})
// This service wraps $http to make sure pending requests are tracked
.service('httpService', ['$http', '$q', 'pendingRequests', function($http, $q, pendingRequests) {
this.get = function(url) {
var canceller = $q.defer();
pendingRequests.add({
url: url,
canceller: canceller
});
//Request gets cancelled if the timeout-promise is resolved
var requestPromise = $http.get(url, { timeout: canceller.promise });
//Once a request has failed or succeeded, remove it from the pending list
requestPromise.finally(function() {
pendingRequests.remove(url);
});
return requestPromise;
}
}])
// The controller just helps generate requests and keep a visual track of pending ones
.controller('AppCtrl', ['$scope', 'httpService', 'pendingRequests', function($scope, httpService, pendingRequests) {
$scope.requests = [];
$scope.$watch(function() {
return pendingRequests.get();
}, function(pending) {
$scope.requests = pending;
})
var counter = 1;
$scope.addRequests = function() {
for (var i = 0, l = 9; i < l; i++) {
httpService.get('https://public.opencpu.org/ocpu/library/?foo=' + counter++);
}
};
$scope.cancelAll = function() {
pendingRequests.cancelAll();
}
}]);
Solution 2:
You can use $http.pendingRequests
to do that.
First, when you make request, do this:
var cancel = $q.defer();
var request = {
method: method,
url: requestUrl,
data: data,
timeout: cancel.promise, // cancel promise, standard thing in $http request
cancel: cancel // this is where we do our magic
};
$http(request).then(.....);
Now, we cancel all our pending requests in $routeChangeStart
$rootScope.$on('$routeChangeStart', function (event, next, current) {
$http.pendingRequests.forEach(function(request) {
if (request.cancel) {
request.cancel.resolve();
}
});
});
This way you can also 'protect' certain request from being cancelled by simply not providing 'cancel' field in request.
Solution 3:
I think this is the best solution to abort requests. It's using an interceptor and $routeChangeSuccess event. http://blog.xebia.com/cancelling-http-requests-for-fun-and-profit/
Solution 4:
Please notice that im new with Angular so this may not be optimal. Another solution could be: on the $http request adding the "timeout" argument, Docs I did it this way:
In a factory where I call all my Rest services, have this logic.
module.factory('myactory', ['$http', '$q', function ($http, $q) {
var canceler = $q.defer();
var urlBase = '/api/blabla';
var factory = {};
factory.CANCEL_REQUESTS = function () {
canceler.resolve();
this.ENABLE_REQUESTS();
};
factory.ENABLE_REQUESTS = function () {
canceler = $q.defer();
};
factory.myMethod = function () {
return $http.get(urlBase, {timeout: canceler.promise});
};
factory.myOtherMethod= function () {
return $http.post(urlBase, {a:a, b:b}, {timeout: canceler.promise});
};
return factory;
}]);
and on the angular app configuration I have:
return angular.module('app', ['ngRoute', 'ngSanitize', 'app.controllers', 'app.factories',
'app.filters', 'app.directives', 'ui.bootstrap', 'ngGeolocation', 'ui.select' ])
.run(['$location', '$rootScope', 'myFactory', function($location, $rootScope, myFactory) {
$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
myFactory.CANCEL_REQUESTS();
$rootScope.title = current.$$route.title;
});
}]);
This way it catches all the "route" changes and stops all the request configured with that "timer" so you can select what is critical for you.
I hope it helps to someone. Regards