How to intercept convenience shortcuts of $http in AngularJS - angularjs

I have the following function (credit) that wraps an AngularJS $http function in a way that it invokes browser XHR when running on desktop, but invokes cordova-plugin-advanced-http if on mobile.
It seems that this works when I use $http({method:'get/post'}...) but doesn't work if I call the convenience shortcuts like $http.get(...)
Can someone suggest what modification I need to make?
$provide.decorator('$http', ['$delegate', '$q', function($delegate, $q) {
// create function which overrides $http function
var $http = $delegate;
var wrapper = function () {
var url = arguments[0].url;
var method = arguments[0].method;
var isOutgoingRequest = /^(http|https):\/\//.test(url);
if (window.cordova && isOutgoingRequest) {
console.log ("**** -->"+method+"<-- using native HTTP with:"+url);
var d = $q.defer();
var options = {
method: method,
data: arguments[0].data,
headers: arguments[0].headers,
timeout: arguments[0].timeout
};
cordova.plugin.http.sendRequest(url,options,
function (succ) {
console.log ("*** Inside native HTTP success with:"+JSON.stringify(succ));
try {
if (options.headers && options.headers['x-parse']=='text')
d.resolve({"data":succ.data});
else
d.resolve({"data":JSON.parse(succ.data)});
return d.promise;
}
catch (e) {
d.resolve({"data":succ.data});
return d.promise;
}
},
function (err) {
console.log ("*** Inside native HTTP error");
d.reject(err);
return d.promise;
});
return d.promise;
}
else {
console.log ("**** "+method+" using XHR HTTP for "+url);
return $http.apply($http, arguments);
}
};
Object.keys($http).filter(function (key) {
return (typeof $http[key] === 'function');
}).forEach(function (key) {
wrapper[key] = function () {
// Apply global changes to arguments, or perform other
// nefarious acts.
// console.log ("KEY="+key);
return $http[key].apply($http, arguments);
};
});
return wrapper;
}]);

If I understood your intent correctly, the way you're assigning the HTTP methods that hang off wrapper won't invoke the contents of your wrapper function.
Note that the parameters of the $http convenience functions vary.
Examples:
GET is described as: get(url, [config])
POST is described as: post(url, data, [config])
With the above in mind, here's one way of delegating back to your wrapper function that switches between XHR and the Cordova plugin when the $http convenience methods are used:
wrapper[key] = function () {
var url = arguments[0];
if (['get', 'delete', 'head', 'jsonp'].indexOf(key) !== -1) {
// arguments[1] == config
return wrapper(Object.assign({
method: key,
url: url,
}, arguments[1]));
} else {
// POST, PUT, PATCH
// arguments[1] == data
// arguments[2] == config
return wrapper(Object.assign({
data: arguments[1],
method: key,
url: url,
}, arguments[2]));
}
};

Here is a working solution I eventually arrived at.
// Wraps around $http that switches between browser XHR
// or cordova-advanced-http based on if cordova is available
// credits:
// a) https://www.exratione.com/2013/08/angularjs-wrapping-http-for-fun-and-profit/
// b) https://gist.github.com/adamreisnz/354364e2a58786e2be71
$provide.decorator('$http', ['$delegate', '$q', function($delegate, $q) {
// create function which overrides $http function
var $http = $delegate;
var wrapper = function () {
var url;
var method;
url = arguments[0].url;
method = arguments[0].method;
var isOutgoingRequest = /^(http|https):\/\//.test(url);
if (window.cordova && isOutgoingRequest) {
console.log ("**** -->"+method+"<-- using native HTTP with:"+encodeURI(url));
var d = $q.defer();
var options = {
method: method,
data: arguments[0].data,
headers: arguments[0].headers,
timeout: arguments[0].timeout,
responseType: arguments[0].responseType
};
cordova.plugin.http.sendRequest(encodeURI(url),options,
function (succ) {
// automatic JSON parse if no responseType: text
// fall back to text if JSON parse fails too
if (options.responseType =='text') {
// don't parse into JSON
d.resolve({"data":succ.data});
return d.promise;
}
else {
try {
d.resolve({"data":JSON.parse(succ.data)});
return d.promise;
}
catch (e) {
console.log ("*** Native HTTP response: JSON parsing failed for "+url+", returning text");
d.resolve({"data":succ.data});
return d.promise;
}
}
},
function (err) {
console.log ("*** Inside native HTTP error: "+JSON.stringify(err));
d.reject(err);
return d.promise;
});
return d.promise;
}
else { // not cordova, so lets go back to default http
console.log ("**** "+method+" using XHR HTTP for "+url);
return $http.apply($http, arguments);
}
};
// wrap around all HTTP methods
Object.keys($http).filter(function (key) {
return (typeof $http[key] === 'function');
}).forEach(function (key) {
wrapper[key] = function () {
return $http[key].apply($http, arguments);
};
});
// wrap convenience functions
$delegate.get = function (url,config) {
return wrapper(angular.extend(config || {}, {
method: 'get',
url: url
}));
};
$delegate.post = function (url,data,config) {
return wrapper(angular.extend(config || {}, {
method: 'post',
url: url,
data:data
}));
};
$delegate.delete = function (url,config) {
return wrapper(angular.extend(config || {}, {
method: 'delete',
url: url
}));
};
return wrapper;
}]);

Related

How to call multiple GET requests using a resource factory in angular?

Here is my code for factory method using $resource
(function () {
"use strict";
angular.module("common.services")
.factory("lookupResource", ["$resource", "appsettings", lookupResource])
function lookupResource($resource, appsettings) {
return {
lookupUserRoles: $resource(appsettings.serverpath + "api/UserRoles", null,
{
'userRoles': { method: 'get' }
}),
lookupStaffTypes: $resource(appsettings.serverpath + "api/StaffTypes", null,
{
'staffTypes': { method: 'get' }
})
}
}
})();
I am trying to call the lookupStaffTypes using below code but it gives no data or error. What am I missing here?
lookupResource.lookupStaffTypes.staffTypes( function (data) {
var test = data;
},
function (response) {
vm.message = response.statusText + "\r\n"
if (response.data.exceptionMessage)
{ vm.message += response.data.exceptionMessage }
//validation errors
if (response.data.error) {
vm.message += response.data.error;
}
});
Where as I can call like this and it gives me data:
var staffTypes = $http.get(appsettings.serverpath + "api/StaffTypes").then(function (dataResponse) {
var qwe = dataResponse;
for (var i = 0; i < dataResponse.data.length; i++) {
$scope.staffTypeList.push(dataResponse.data[i]);
}
}, function (response) {
vm.message = response.statusText + "\r\n"
if (response.data.exceptionMessage)
{ vm.message += response.data.exceptionMessage }
//validation errors
if (response.data.error) {
vm.message += response.data.error;
}
});
I am new to angular and any help would be highly appreciated!
I recommend avoiding the ngResource module and just using the $http service directly. If a server implements a RESTful API, I recommend using Restangular.
That said, for ngResource:
If the server returns an array, it is important to define the action method with isArray: true:
app.factory("lookupResource", ["$resource", "appsettings", lookupResource])
function lookupResource($resource, appsettings) {
return {
lookupStaffTypes: $resource(appsettings.serverpath + "api/StaffTypes", null,
{
'staffTypes': { method: 'get',
isArray: true }
})
}
}
Invoke it with:
$scope.staffTypeList = lookupResource.lookupStaffTypes.staffTypes();
//OR
$scope.staffTypeList = lookupResource.lookupStaffTypes.query();
It is important to realize that invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data.
If the service expects an object but receives an array, or vice versa, it will generate a $resource:badcfg error. If the service returns a boolean or a string, the action method will quietly return nothing.
Use the $promise propery of the received object (or array) to chain sequential operations:
$scope.staffTypeList.$promise.then(function(staffTypeList) {
console.log(staffTypeList);
//Do more stuff
});

Flag intentional http request cancellation of api requests with same url

This is http interceptor service that should handle cancellation of previous requests with the same url. I have a problem with handling rejection after calling 'resolve' method which falls into a 'catch' handler of my angular data service.
How to flag this cancellation as intentional, so my data service does not indicate that an error occurred on server side? I don't want to display error message in this case.
(function () {
'use strict';
angular
.module('common')
.factory('abortService', abortService);
abortService.$inject = ['$q', '$filter'];
function abortService($q, $filter) {
var service = {
request: request,
};
var error = "";
var currentRequests = [];
return service;
function request(request) {
if (!request.url.includes('api/')) return request;
var deferred = $q.defer();
request.timeout = deferred.promise;
var existingRequests = $filter('filter')(currentRequests, { url: request.url }, true);
existingRequests.forEach(function (request) {
request.promiseObj.resolve('intentional_reject');//how to flag as intentional reject??
});
currentRequests.push({ url: request.url, promiseObj: deferred });
return request;
}
};})();
And here is my angular data service method:
function getBalanceDueSummary(businessPartnerCode) {
return $http({
method: "get",
url: "/api/BusinessPartnerAPI/GetBalanceDueSummary",
params:
{
businessPartnerCode: businessPartnerCode,
}
})
.then(complete)
.catch(failed);
function complete(response) {
return response.data;
}
function failed(error) { //here my cancellation falls into, but i have no data which indicates intentional cancellation
//logService.exception displays a toastr message when error occurrs
logService.exception(error, 'GetBalanceDueSummary');
return $q.reject(error);
}
}
I have done some digging and found the value 'intentional_reject' in object:
error.Config.timeout.$$state.value
function getBalanceDueSummary(businessPartnerCode) {
return $http({
method: "get",
url: "/api/BusinessPartnerAPI/GetBalanceDueSummary",
params:
{
businessPartnerCode: businessPartnerCode,
}
})
.then(complete)
.catch(failed);
function complete(response) {
return response.data;
}
function failed(error) {
if(!(error.Config.timeout.$$state.value &&
error.Config.timeout.$$state.value=='intentional_reject'))
logService.exception(error, 'GetBalanceDueSummary');
return $q.reject(error);
}
}
I doubt this is the best solution for this but I could not set response status manually...

incercept $http request to modify api call urls

var app = angular.module('app');
// register the interceptor as a service
app.factory('myHttpInterceptor', function($q ) {
return {
'request': function(config) {
return config;
}
};
});
I am trying to modify the urls of api calls and append the api url to the start of the ajax calls in the interceptor insted of each service function like
function getAssesmentResults(params) {
return $http.get(url.api + '/results', {params: params})
.then(function(response) {
return response.data;
});
}
However the interceptor intercepts all http requests like .css or .html or .json files. What is a good way to modify the urls in the interceptor without modifying other http requests?
$http has a facility to intercept and rewrite URLs that is configured on the $httpProvider. When I run in production, I have an extra token: '/rest/' compared with development mode and I detect production mode ( prefix packing ) in the interceptor. This is in my app.js
var rest_srvr = 'http://dev-pc.example.com:8000'
app.factory('REST_Interceptor',[
'$location',
function($location) {
var request = function(config) {
if (RegExp('packing','i').test(window.location.host)) {
return config
}
var rest_request_regex = new RegExp('^.*?/rest/(.*)$')
//console.log('config.url=%s',config.url)
config.url = config.url.replace(rest_request_regex,rest_srvr+'/$1')
var files_request_regex = new RegExp('^/(files/(.*))$')
config.url = config.url.replace(files_request_regex,rest_srvr+'/$1')
//console.log(' is now config.url=%s',config.url)
return config
}
var translate_subpath = function(subpath) {
return request({url:'https://'+$location.host()+subpath}).url
}
return {
request: request,
translate_subpath: translate_subpath
}
}])
app.config([
'$httpProvider','$cookiesProvider',
function($httpProvider, $cookiesProvider) {
if (!RegExp('packing','i').test(window.location.host)) {
$httpProvider.interceptors.push('REST_Interceptor')
}
}])
I would create a service that wraps the $http. Then in your code you'll always call this wrap instead of $http and will be able to do whatever you want with the request before it is sent. Just a simple example:
module.factory('myHttp', function($http){
return {
get: function(url, params){
var newUrl = "base-api-url/" + url;
return $http.get(newUrl, params);
}
}
})
Use the generic service for this:
Generic service
appCless.factory("$comum", function($http, $q, $injector) {
function ajax(url, parametros, metodo) {
var requisicao = $http({
method: metodo,
url: url,
data:parametros
});
var promessa = requisicao.then(
function(resposta) {
return(resposta.data);
},
function(resposta) {
return($q.reject("Something went wrong"));
}
);
return promessa;
}
return({
ajax:ajax
});
});
Service
app.factory("$categoriaproduto", function($comum) {
var categoria;
return {
buscar : function(neoId) {
var promessa = $comum.ajax("/fusion/services/roi/category/product/search", "", "POST");
promessa.then(function(req) {
categoria = req.data;
});
return promessa;
},
cache : function() {
return categoria;
}
};
});

Cancelling GET Request to server using Restangular

I have create a UserService as follows:
angular.module('nrApp').factory('userService', ['Restangular', 'UserModel', 'DSCacheFactory', function (Restangular, UserModel, DSCacheFactory) {
// Create a new cache called "profileCache"
var userCache = DSCacheFactory('userCache', {
maxAge: 3600000,
deleteOnExpire: 'aggressive',
storageMode: 'localStorage', // This cache will sync itself with `localStorage`.
onExpire: function (key, value) {
Restangular.oneUrl('users', key).get().then(function(data) {
userCache.put(key, data);
});
}
});
Restangular.extendModel('users', function(obj) {
return UserModel.mixInto(obj);
});
Restangular.addRequestInterceptor(function(element, operation, what, url) {
if(operation === 'get') {
debugger;
//Check the cache to see if the resource is already cached
var data = userCache.get(url);
//If cache object does exist, return it
if(data !== undefined) {
angular.extend(element, data);
}
return element;
}
});
Restangular.addResponseInterceptor(function(data, operation, what, url, response) {
//Cache the response from a get method
if(operation === 'get') {
debugger;
userCache.put(url, data);
}
//Unvalidate the cache when a 'put', 'post' and 'delete' is performed to update the cached version.
if (operation === 'put' || operation === 'post' || operation === 'delete') {
userCache.destroy();
}
return response;
});
return Restangular.service('users');
}]);
From the comments it can be seen that what I am trying to achieve is whenever a Get request is performed through this service using Restangular the local cache is checked and if the cache returns an object it is extended into the restangular element. The flow that want to achieve is that of cancelling the request to the sever when a cache object is found for that request.
However without any luck the addResponseInterceptor method still executes even though the object was found in the cache.
Are there any possible solutions to cancelling the request to the sever during a 'Get' request?
Thanks! :)
One way to go about it would be to cancel it via httpConfig. Restangular gives you httpConfig object as a parameter in the addFullRequestInterceptor method. You could use that like in the following:
RestangularProvider.addFullRequestInterceptor(function(element, operation, what, url, headers, params, httpConfig ) {
...
if found in cache {
var defer = $q.defer();
httpConfig.timeOut = defer.promise;
defer.resolve();
}
...
}
Hope this helps.
I solved the particular issue of returning cached data if available through an angular-cache CacheFactory instance by simply changing the httpConfig settings in the RequestInterceptor. Example shown below:
angular.module('App')
.factory('Countries', function (Restangular, CacheFactory, $q) {
var countryCache;
var countryService;
// Check to make sure the cache doesn't already exist
if (!CacheFactory.get('countryCache')) {
countryCache = CacheFactory('countryCache', { maxAge: 60 * 60 * 1000 });
}
if (!countryService) {
countryService = Restangular.service('countries');
Restangular.addFullRequestInterceptor(function(element, operation, what, url, headers, params, httpConfig) {
if (what === 'countries') {
switch (operation) {
case 'getList':
httpConfig.cache = countryCache;
break;
default:
break;
}
}
return {
element: element,
headers: headers,
params: params,
httpConfig: httpConfig
};
});
}
return countryService;
});
You can decorate $http to prevent multiple request to the same url. Restangular use $http, don't need to adding fullRequestIntercepter to cancel request, because this prevent request before send.
$provide.decorator('$http', function ($delegate, $cacheFactory, $rootScope) {
var $http = $delegate;
var customCache = $cacheFactory('customCache');
var wrapper = function () {
var key = arguments[0].url;
var requestPromise = customCache.get(key);
if (!requestPromise){
$rootScope.requestCount++;
requestPromise = $http.apply($http, arguments);
requestPromise.then(function(){
customCache.remove(key);
});
customCache.put(key, requestPromise)
}
return requestPromise;
};
Object.keys($http).filter(function (key) {
return (typeof $http[key] === 'function');
}).forEach(function (key) {
wrapper[key] = function () {
return $http[key].apply($http, arguments);
};
});
return wrapper;
});
Example here

How to cancel an $http request in AngularJS?

Given a Ajax request in AngularJS
$http.get("/backend/").success(callback);
what is the most effective way to cancel that request if another request is launched (same backend, different parameters for instance).
This feature was added to the 1.1.5 release via a timeout parameter:
var canceler = $q.defer();
$http.get('/someUrl', {timeout: canceler.promise}).success(successCallback);
// later...
canceler.resolve(); // Aborts the $http request if it isn't finished.
Cancelling Angular $http Ajax with the timeout property doesn't work in Angular 1.3.15.
For those that cannot wait for this to be fixed I'm sharing a jQuery Ajax solution wrapped in Angular.
The solution involves two services:
HttpService (a wrapper around the jQuery Ajax function);
PendingRequestsService (tracks the pending/open Ajax requests)
Here goes the PendingRequestsService service:
(function (angular) {
'use strict';
var app = angular.module('app');
app.service('PendingRequestsService', ["$log", function ($log) {
var $this = this;
var 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.xhr.abort();
p.deferred.reject();
});
pending.length = 0;
};
}]);})(window.angular);
The HttpService service:
(function (angular) {
'use strict';
var app = angular.module('app');
app.service('HttpService', ['$http', '$q', "$log", 'PendingRequestsService', function ($http, $q, $log, pendingRequests) {
this.post = function (url, params) {
var deferred = $q.defer();
var xhr = $.ASI.callMethod({
url: url,
data: params,
error: function() {
$log.log("ajax error");
}
});
pendingRequests.add({
url: url,
xhr: xhr,
deferred: deferred
});
xhr.done(function (data, textStatus, jqXhr) {
deferred.resolve(data);
})
.fail(function (jqXhr, textStatus, errorThrown) {
deferred.reject(errorThrown);
}).always(function (dataOrjqXhr, textStatus, jqXhrErrorThrown) {
//Once a request has failed or succeeded, remove it from the pending list
pendingRequests.remove(url);
});
return deferred.promise;
}
}]);
})(window.angular);
Later in your service when you are loading data you would use the HttpService instead of $http:
(function (angular) {
angular.module('app').service('dataService', ["HttpService", function (httpService) {
this.getResources = function (params) {
return httpService.post('/serverMethod', { param: params });
};
}]);
})(window.angular);
Later in your code you would like to load the data:
(function (angular) {
var app = angular.module('app');
app.controller('YourController', ["DataService", "PendingRequestsService", function (httpService, pendingRequestsService) {
dataService
.getResources(params)
.then(function (data) {
// do stuff
});
...
// later that day cancel requests
pendingRequestsService.cancelAll();
}]);
})(window.angular);
Cancelation of requests issued with $http is not supported with the current version of AngularJS. There is a pull request opened to add this capability but this PR wasn't reviewed yet so it is not clear if its going to make it into AngularJS core.
If you want to cancel pending requests on stateChangeStart with ui-router, you can use something like this:
// in service
var deferred = $q.defer();
var scope = this;
$http.get(URL, {timeout : deferred.promise, cancel : deferred}).success(function(data){
//do something
deferred.resolve(dataUsage);
}).error(function(){
deferred.reject();
});
return deferred.promise;
// in UIrouter config
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
//To cancel pending request when change state
angular.forEach($http.pendingRequests, function(request) {
if (request.cancel && request.timeout) {
request.cancel.resolve();
}
});
});
For some reason config.timeout doesn't work for me. I used this approach:
let cancelRequest = $q.defer();
let cancelPromise = cancelRequest.promise;
let httpPromise = $http.get(...);
$q.race({ cancelPromise, httpPromise })
.then(function (result) {
...
});
And cancelRequest.resolve() to cancel. Actually it doesn't not cancel a request but you don't get unnecessary response at least.
Hope this helps.
This enhances the accepted answer by decorating the $http service with an abort method as follows ...
'use strict';
angular.module('admin')
.config(["$provide", function ($provide) {
$provide.decorator('$http', ["$delegate", "$q", function ($delegate, $q) {
var getFn = $delegate.get;
var cancelerMap = {};
function getCancelerKey(method, url) {
var formattedMethod = method.toLowerCase();
var formattedUrl = encodeURI(url).toLowerCase().split("?")[0];
return formattedMethod + "~" + formattedUrl;
}
$delegate.get = function () {
var cancelerKey, canceler, method;
var args = [].slice.call(arguments);
var url = args[0];
var config = args[1] || {};
if (config.timeout == null) {
method = "GET";
cancelerKey = getCancelerKey(method, url);
canceler = $q.defer();
cancelerMap[cancelerKey] = canceler;
config.timeout = canceler.promise;
args[1] = config;
}
return getFn.apply(null, args);
};
$delegate.abort = function (request) {
console.log("aborting");
var cancelerKey, canceler;
cancelerKey = getCancelerKey(request.method, request.url);
canceler = cancelerMap[cancelerKey];
if (canceler != null) {
console.log("aborting", cancelerKey);
if (request.timeout != null && typeof request.timeout !== "number") {
canceler.resolve();
delete cancelerMap[cancelerKey];
}
}
};
return $delegate;
}]);
}]);
WHAT IS THIS CODE DOING?
To cancel a request a "promise" timeout must be set.
If no timeout is set on the HTTP request then the code adds a "promise" timeout.
(If a timeout is set already then nothing is changed).
However, to resolve the promise we need a handle on the "deferred".
We thus use a map so we can retrieve the "deferred" later.
When we call the abort method, the "deferred" is retrieved from the map and then we call the resolve method to cancel the http request.
Hope this helps someone.
LIMITATIONS
Currently this only works for $http.get but you can add code for $http.post and so on
HOW TO USE ...
You can then use it, for example, on state change, as follows ...
rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
angular.forEach($http.pendingRequests, function (request) {
$http.abort(request);
});
});
here is a version that handles multiple requests, also checks for cancelled status in callback to suppress errors in error block. (in Typescript)
controller level:
requests = new Map<string, ng.IDeferred<{}>>();
in my http get:
getSomething(): void {
let url = '/api/someaction';
this.cancel(url); // cancel if this url is in progress
var req = this.$q.defer();
this.requests.set(url, req);
let config: ng.IRequestShortcutConfig = {
params: { id: someId}
, timeout: req.promise // <--- promise to trigger cancellation
};
this.$http.post(url, this.getPayload(), config).then(
promiseValue => this.updateEditor(promiseValue.data as IEditor),
reason => {
// if legitimate exception, show error in UI
if (!this.isCancelled(req)) {
this.showError(url, reason)
}
},
).finally(() => { });
}
helper methods
cancel(url: string) {
this.requests.forEach((req,key) => {
if (key == url)
req.resolve('cancelled');
});
this.requests.delete(url);
}
isCancelled(req: ng.IDeferred<{}>) {
var p = req.promise as any; // as any because typings are missing $$state
return p.$$state && p.$$state.value == 'cancelled';
}
now looking at the network tab, i see that it works beatuifully. i called the method 4 times and only the last one went through.
You can add a custom function to the $http service using a "decorator" that would add the abort() function to your promises.
Here's some working code:
app.config(function($provide) {
$provide.decorator('$http', function $logDecorator($delegate, $q) {
$delegate.with_abort = function(options) {
let abort_defer = $q.defer();
let new_options = angular.copy(options);
new_options.timeout = abort_defer.promise;
let do_throw_error = false;
let http_promise = $delegate(new_options).then(
response => response,
error => {
if(do_throw_error) return $q.reject(error);
return $q(() => null); // prevent promise chain propagation
});
let real_then = http_promise.then;
let then_function = function () {
return mod_promise(real_then.apply(this, arguments));
};
function mod_promise(promise) {
promise.then = then_function;
promise.abort = (do_throw_error_param = false) => {
do_throw_error = do_throw_error_param;
abort_defer.resolve();
};
return promise;
}
return mod_promise(http_promise);
}
return $delegate;
});
});
This code uses angularjs's decorator functionality to add a with_abort() function to the $http service.
with_abort() uses $http timeout option that allows you to abort an http request.
The returned promise is modified to include an abort() function. It also has code to make sure that the abort() works even if you chain promises.
Here is an example of how you would use it:
// your original code
$http({ method: 'GET', url: '/names' }).then(names => {
do_something(names));
});
// new code with ability to abort
var promise = $http.with_abort({ method: 'GET', url: '/names' }).then(
function(names) {
do_something(names));
});
promise.abort(); // if you want to abort
By default when you call abort() the request gets canceled and none of the promise handlers run.
If you want your error handlers to be called pass true to abort(true).
In your error handler you can check if the "error" was due to an "abort" by checking the xhrStatus property. Here's an example:
var promise = $http.with_abort({ method: 'GET', url: '/names' }).then(
function(names) {
do_something(names));
},
function(error) {
if (er.xhrStatus === "abort") return;
});

Resources