I am using http Interceptor to intercept each http request in my application.
But I am getting Circular dependency found: $http <- APIInterceptor <- $http <- $templateRequest <- $compile
here is my Service code:
mPosServices.factory('mosServiceFactory', function ($http, $rootScope, $cookies, $q) {
return{
refresh_token: function () {
var refreshToken = $http({
method: "get",
url: "myservice/oauth/token?grant_type=refresh_token&client_id=restapp&client_secret=restapp&refresh_token=" + $cookies.get('refresh_token'),
})
return refreshToken;
}
});
mPosServices.service('APIInterceptor', ['mosServiceFactory','$cookies',function (mosServiceFactory,$cookies) {
var service = this;
service.request = function (config) {
if (!$cookies.get('access_token')) { //if access_token cookie does not exist
mosServiceFactory.refresh_token().then(function (response) {
var date = new Date();
date.setTime(date.getTime() + (response.data.expiresIn * 1000));
$cookies.remove('access_token');
$cookies.put('access_token', response.data.value, {expires: date});
$cookies.put('refresh_token', response.data.refreshToken.value);
}); //call the refresh_token function first
}
return config;
};
service.responseError = function (response) {
return response;
};
}]);
and pushing it as:
$httpProvider.interceptors.push('APIInterceptor');
in config function.
Am I doing something wrong here?
Even I tried to inject $http manually using
$injector
, but getting same error.
Kindly Help me.
You indeed need to add use $injector to get mosServiceFactory instance inside of interceptor. But this is not all you need to do. You also need to make sure you don't fall into infinite request loop because interceptor also makes a request. What you can do is to check if current request is the one for token refresh and if so don't fire one more request, I'm checking the URL for this.
One more important thing to mention. You need to return promise object from interceptor which resolves to original request config. This way it guaranties that intercepted request will be reissued after token is retrieved.
All together will look like this:
mPosServices.service('APIInterceptor', ['$injector', '$cookies', function($injector, $cookies) {
var service = this;
service.request = function(config) {
if (!$cookies.get('access_token') && config.url.indexOf('myservice/oauth/token?grant_type=') === -1) {
return $injector.get('mosServiceFactory').refresh_token().then(function(response) {
var date = new Date();
date.setTime(date.getTime() + (response.data.expiresIn * 1000));
$cookies.remove('access_token');
$cookies.put('access_token', response.data.value, {
expires: date
});
$cookies.put('refresh_token', response.data.refreshToken.value);
}).then(function() {
return config; // <-- token is refreshed, reissue original request
});
}
return config;
};
service.responseError = function(response) {
return response;
};
}]);
Check the demo I was testing solution on to see how it recovers original request after token is loaded.
Demo: http://plnkr.co/edit/1Aey2PThZQ4Y8IRZuVOl?p=preview
Related
I'm trying to implement JWT token in this project. For that I used Authorization header in the $resource, like as this.
When I login on UI state "A", after getting logged in, I put the token in the localStorage as
$localStorage.token = data.token;
When I go to UI state "B" of the page, it uses the following service and send a request with no token. But on refreshing the page, it sends the same request with the token.
angular.module('BlurAdmin')
.factory('valueService', ['Token','$localStorage','$resource', 'endpoint', function(Token,$localStorage, $resource, endpoint) {
return {
getValues: $resource(endpoint + '/admin/getvalues', null, {
'get': {
method: 'GET',
headers:{'Authorization':'Bearer '+$localStorage.token}
}
}),
}
}]);
I think the service stores the $localStorage.token value initially and uses that even when the state changes. But when the page is reloaded, it gets the $localStorage.token value again.
How do I force this service to get the $localStorage.token value everytime the UI state changes?
Thanks in Advance!
To have the resource compute the header value on each XHR GET operation, furnish a function instead of a value:
angular.module('BlurAdmin')
.factory('valueService', ['Token','$localStorage','$resource', 'endpoint', function(Token,$localStorage, $resource, endpoint) {
return {
getValues: $resource(endpoint + '/admin/getvalues', null, {
'get': {
method: 'GET',
//headers:{'Authorization':'Bearer '+$localStorage.token}
headers:
{'Authorization':
function () {
return 'Bearer '+$localStorage.token;
}
}
}
}),
}
}]);
When a value is furnished, the header value is computed when the resource is created. When a function is furnished, the header value is computed each time the resource get method is called.
headers – {Object} – Map of strings or functions which return strings representing HTTP headers to send to the server. If the return value of a function is null, the header will not be sent. Functions accept a config object as an argument.
-- AngularJS $http Service API Reference - Usage
Your problem is that the resource definition is provided at the time of creation (before you have saved a token). To avoid this behavior, simply create a wrapper function and parse your token into it.
angular.module('BlurAdmin')
.factory('valueService', ['Token','$localStorage','$resource', 'endpoint', function(Token,$localStorage, $resource, endpoint) {
return function (token) {
return $resource(endpoint + '/admin/getvalues', {}, {
get: {
method: 'GET',
headers:{'Authorization':'Bearer ' + token}
}
})
}
}]);
Call your factory function like:
valueService($localStorage.token).get(function (result) {
console.log(result);
}, function (error) {
console.log(result);
});
If you are using the header with many API calls, It is better to add it in a commom place rather than adding it with each API
Please refer : intercepter https://docs.angularjs.org/api/ng/service/$http
angular.module('utimf.services', ['ngResource', 'ng.deviceDetector'])
.factory('UtimfHttpIntercepter', UtimfHttpIntercepter)
UtimfHttpIntercepter.$inject = ['$q', '$localStorage'];
function UtimfHttpIntercepter($q, $localStorage) {
var authFactory = {};
var _request = function (config) {
config.headers = config.headers || {}; // change/add hearders
config.data = config.data || {}; // change/add post data
config.params = config.params || {}; //change/add querystring params
config.headers['Authorization'] = 'Bearer '+$localStorage.token; // New headers are added here
return config || $q.when(config);
}
var _requestError = function (rejection) {
// handle if there is a request error
return $q.reject(rejection);
}
var _response = function(response){
// handle your response
return response || $q.when(response);
}
var _responseError = function (rejection) {
// handle if there is a request error
return $q.reject(rejection);
}
authFactory.request = _request;
authFactory.requestError = _requestError;
authFactory.response = _response;
authFactory.responseError = _responseError;
return authFactory;
}
and add $httpProvider.interceptors.push('UtimfHttpIntercepter'); in your config
How can I stop a request in Angularjs interceptor.
Is there any way to do that?
I tried using promises and sending reject instead of resolve !
.factory('connectionInterceptor', ['$q', '$timeout',
function($q, $timeout) {
var connectionInterceptor = {
request: function(config) {
var q = $q.defer();
$timeout(function() {
q.reject();
}, 2000)
return q.promise;
// return config;
}
}
return connectionInterceptor;
}
])
.config(function($httpProvider) {
$httpProvider.interceptors.push('connectionInterceptor');
});
I ended up bypassing angular XHR call with the following angular Interceptor:
function HttpSessionExpiredInterceptor(sessionService) {
return {
request: function(config) {
if (sessionService.hasExpired()) {
/* Avoid any other XHR call. Trick angular into thinking it's a GET request.
* This way the caching mechanism can kick in and bypass the XHR call.
* We return an empty response because, at this point, we do not care about the
* behaviour of the app. */
if (_.startsWith(config.url, '/your-app-base-path/')) {
config.method = 'GET';
config.cache = {
get: function() {
return null;
}
};
}
}
return config;
}
};
}
This way, any request, POST, PUT, ... is transformed as a GET so that the caching mechanism can be
used by angular. At this point, you can use your own caching mechanism, in my case, when session
expires, I do not care anymore about what to return.
The $http service has an options
timeout to do the job.
you can do like:
angular.module('myApp')
.factory('httpInterceptor', ['$q', '$location',function ($q, $location) {
var canceller = $q.defer();
return {
'request': function(config) {
// promise that should abort the request when resolved.
config.timeout = canceller.promise;
return config;
},
'response': function(response) {
return response;
},
'responseError': function(rejection) {
if (rejection.status === 401) {
canceller.resolve('Unauthorized');
$location.url('/user/signin');
}
if (rejection.status === 403) {
canceller.resolve('Forbidden');
$location.url('/');
}
return $q.reject(rejection);
}
};
}
])
//Http Intercpetor to check auth failures for xhr requests
.config(['$httpProvider',function($httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
}]);
Not sure if it is possible in general. But you can start a $http request with a "canceler".
Here is an example from this answer:
var canceler = $q.defer();
$http.get('/someUrl', {timeout: canceler.promise}).success(successCallback);
// later...
canceler.resolve(); // Aborts the $http request if it isn't finished.
So if you have control over the way that you start your request, this might be an option.
I just ended up in returning as an empty object
'request': function request(config) {
if(shouldCancelThisRequest){
return {};
}
return config;
}
Here is what works for me, especially for the purposes of stopping the outgoing request, and mocking the data:
app
.factory("connectionInterceptor", [
"$q",
function ($q) {
return {
request: function (config) {
// you can intercept a url here with (config.url == 'https://etc...') or regex or use other conditions
if ("conditions met") {
config.method = "GET";
// this is simulating a cache object, or alternatively, you can use a real cache object and pre-register key-value pairs,
// you can then remove the if block above and rely on the cache (but your cache key has to be the exact url string with parameters)
config.cache = {
get: function (key) {
// because of how angularjs $http works, especially older versions, you need a wrapping array to get the data
// back properly to your methods (if your result data happens to be an array). Otherwise, if the result data is an object
// you can pass back that object here without any return codes, status, or headers.
return [200, mockDataResults, {}, "OK"];
},
};
}
return config;
},
};
},
])
.config(function ($httpProvider) {
$httpProvider.interceptors.push("connectionInterceptor");
});
If you are trying to mock a result like
[42, 122, 466]
you need to send an array with some http params back, its just how the ng sendReq() function is written unfortunately. (see line 1414 of https://github.com/angular/angular.js/blob/e41f018959934bfbf982ba996cd654b1fce88d43/src/ng/http.js#L1414 or snippet below)
// from AngularJS http.js
// serving from cache
if (isArray(cachedResp)) {
resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3], cachedResp[4]);
} else {
resolvePromise(cachedResp, 200, {}, 'OK', 'complete');
}
I have a method that calls an angular service and consequently makes an ajax request via the service. I need to make sure that if this is called several times, the previous request in aborted (if it hasn't already been resolved that is).
This method can get called multiple times. This method is actually from ngTable on ngTableParams:
getData = function($defer, params) {
myService.getRecord(params).then(function(res){
...
$defer.resolve(res.Records);
});
}
Here's the method on the service:
this.getRecords = function(params) {
...
return Restangular
.all('/api/records')
.post(filters);
};
If ngTable makes 3 calls I want the first 2 to be aborted (unless of course they returned so fast that it got resolved)
You can abort $http calls via the timeout config property, which can be a Promise, that aborts the request when resolved.
So in restangular, you can do this like
var abort = $q.defer();
Restangular.one('foos', 12345).withHttpConfig({timeout: abort.promise}).get();
abort.resolve();
To integrate it with your usecase, for example, you could have this in your service:
var abortGet;
this.getRecords = function(params) {
...
if (abortGet) abortGet.resolve();
abortGet = $q.defer();
return Restangular
.all('/api/records')
.withHttpConfig({timeout: abortGet.promise})
.post(filters);
}
This way calling getRecords always aborts the previous call if has not been resolved yet!
This is another approach if you want to abort all http requests when change the state for UI-router:
angular
.run(function(HttpHandlerSrv) {
HttpHandlerSrv.abortAllRequestsOn('$stateChangeSuccess');
HttpHandlerSrv.R.setFullRequestInterceptor(function(element, operation, route, url, headers, params, httpConfig) {
httpConfig = httpConfig || {};
if(httpConfig.timeout === undefined) {
httpConfig.timeout = HttpHandlerSrv.newTimeout();
}
return { element: element, params: params, headers: headers, httpConfig: httpConfig };
});
})
.factory('HttpHandlerSrv', HttpHandlerSrv);
/** ngInject */
function HttpHandlerSrv($q, $rootScope, Restangular) {
var requests = [];
return {
R: Restangular,
newTimeout: newTimeout,
abortAllRequests: abortAllRequests,
abortAllRequestsOn: abortAllRequestsOn
};
function newTimeout() {
var request = $q.defer();
requests.push(request);
return request.promise;
}
function abortAllRequests() {
angular.forEach(requests, function(request) {
request.resolve();
});
requests = [];
}
function abortAllRequestsOn(event) {
$rootScope.$on(event, function(event, newUrl, oldUrl) {
if(newUrl !== oldUrl) { abortAllRequests(); }
});
}
}
I can't seem to get the $httpProvider.interceptors to actually intercept. I created a sample on JSFiddle that logs when the interceptor is run and when the $http response is successful. The request interceptor is run after the response is already returned as successful. This seems a bit backwards.
I can't use transformRequest because I need to alter the params in the config. That part isn't shown in the sample.
I'm using AngularJS 1.1.5
http://jsfiddle.net/skeemer/K7DCN/1/
Javascript
var myApp = angular.module('myApp', []);
myApp.factory('httpInterceptor', function ($q) {
return {
request: function (config) {
logIt('- Modify request');
return config || $q.when(config);
}
};
});
myApp.config(function ($httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
});
function MyCtrl($scope, $http) {
// Hit a server that allows cross domain XHR
$http.get('http://server.cors-api.appspot.com/server?id=8057313&enable=true&status=200&credentials=false')
.success(function (data) {
//logIt(data[0].request.url);
logIt('- GET Successful');
});
$scope.name = 'Superhero';
}
// Just the logging
var logs = document.getElementById('logs');
function logIt(msg) {
var e = document.createElement('div');
e.innerHTML = msg;
logs.insertBefore(e, logs.firstChild);
}
HTML
<div ng-controller="MyCtrl">Hello, {{name}}!</div>
<br/>
<div id="logs"></div>
If you want the option to accept/reject a request at interception time you should be using $httpProvider.responseInterceptors, see example below:
$httpProvider.responseInterceptors.push(function($q) {
return function(promise){
var deferred = $q.defer();
promise.then(
function(response){ deferred.reject("I suddenly decided I didn't like that response"); },
function(error){ deferred.reject(error); }
);
return deferred.promise;
};
});
EDIT Didn't read your comment, indeed responseInterceptors is now obsolete an that's how you do it instead:
$httpProvider.interceptors.push(function($q) {
return {
request: function(config){ return config; },
response: function(response) { return $q.reject(response); }
};
});
I learned something useful, thanks
The request interceptor isn't running after the data is returned. It's running before. Your logIt function inserts the newest message at the top. If you change your code to use the $log service, you'll see that the interceptor runs first.
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;
});