AngularJS intercept all $http requests - angularjs

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.

Related

Correct method to Redirect from $http.post in angularjs

Can you tell me what is the correct way to redirect to another page if $http.post returns a specific error code.
Just to add context, I want to redirect to another page if the user is not logged in or authorized to use the API.
function getData(filter) {
var deferred = $q.defer();
var data = JSON.stringify(filter);
$http.post('/myapp/api/getData', data)
.success(function (data, status, headers, config) {
deferred.resolve(data);
})
.error(function (error) {
deferred.reject(error);
});
return deferred.promise;
}
You could do a redirect to the page using $window.location.href, based on the error condition you have.
var app = angular.module("sampleApp", []);
app.controller("sampleController", [
"$scope",
'$window',
'sampleService',
function($scope, $window, sampleService) {
sampleService.getData().then(function(result) {}, function(error) {
if (error.statusCode === 400) {
alert("Error");
$window.location.href = "http://stackoverflow.com"
}
});
}
]);
app.service("sampleService", function() {
this.getData = function() {
var promise = new Promise(function(resolve, reject) {
setTimeout(function() {
reject({
statusCode: 400
});
}, 1000);
});
return promise;
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-App="sampleApp">
<div ng-controller="sampleController">
</div>
</div>
The best way to catch global AuthenticationErrorin angular is with interceptor.
This way you can monitor all request that are sent from angular and check for AuthenticationError.
$provide.factory('AuthErrorInterceptor', function($q, $location) {
return {
'responseError': function(rejection) {
//check for auth error
$location.path('/login');
return $q.reject(rejection);
}
};
});
Example :
$http.post('/myapp/api/getData', data)
.then(function (data) {
if(data.ErrorCode==1)
{
$window.location.href="controllerName/actionName";
}
})
Use a interceptor service in order to centralize all of your rejection request in the same service.
module.config(['$httpProvider', ($httpProvider: ng.IHttpProvider) => {
$httpProvider.interceptors.push('errorService');
}]);
module.factory('errorService', ['$location', function($location) {
var errorService = {
responseError: function(rejection) {
if (rejection === '401') {
$location.path('/login');
}
}
};
return errorService;
}]);
The $http.post is misguiding.
So far the best answer is #Kliment's. Interceptors are the best way to manage what comes before and after http requests.
However, if your end goal is to prevent access to a page, you have to at least use a routing plugin (ngRoute, ui-router) because with the promise idea there will always be a delay between the http request and the response.
Depending on server response time you'll still see the page display for about a second or so.
With ui-router you simply configure a resolve method for each state you want to protect. It could look like this:
.state('protected',
{
url : '/protected_page',
templateUrl : 'secret.html',
resolve: {
loggedin: loggedin
}
})
loggedin refers to a function you define that contains your $http.post call (or better yet a service)
function loggedin($timeout, $q, $location, loginService) {
loginService.then(function(data) {
if(data.status == 401) {
//$timeout(function() { $location.path('/login'); });
return $q.reject();
} else {
return $q.when();
}
});
}
Here this particular service returns a 401 status but you can return anything.
The state will not be resolved (and the page not displayed) until it's accepted or rejected.
You can redirect directly from there if you want, although it's not very elegant.
ui-router gives you another option with default redirection:
if (tokenIsValid())
$urlRouterProvider.otherwise("/home");
else
$urlRouterProvider.otherwise("/login");
With otherwise you tell ui-router to go to certain urls if no state exists for a particular request or if a resolve has been rejected.
On another subject, your http request is badly written.
.success and .error are deprecated and you don't need to create a promise ($q) over an $http request that itself already returns a promise.
You have a good example in the documentation linked above.
You can redirect to page on unauthorized access of a user based on the status code which you can return from your API call.
$http({
method: "POST",
url: 'Api/login',
headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}
}).success(function (data,status) {
if(status==200){
alert('Successfully logged in');
$location.path('/dashboard'); //You can use this if you are defined your states.
}
}).error(function (data,status) {
if(status==403||status==403){ //Based on what error code you are returning from API
$location.path('/login');// if states are defined else
$window.location.href = "https://www.google.com";
}
});
First of all Nice Question , In this scenario You Can use $location , $state If it is external url You can use $window.location.href ... I would recommend $location and it is the best way ...
Please See the link for further Using $window or $location to Redirect in AngularJS
function getData(filter) {
var deferred = $q.defer();
var data = JSON.stringify(filter);
$http.post('/myapp/api/getData', data)
.success(function (data, status, headers, config) {
deferred.resolve(data);
if(data.errorcode==9999) // Define Your Error Code in Server
{
$location.path('/login'); // You Can Set Your Own Router
} })
.error(function (error) {
$location.path('/login'); // You Can Set Your Own Router
deferred.reject(error);
});
return deferred.promise;
}
Preferably use $location or $state ...

angularjs how to catch http response data that sent by browser

As title, I want to catch Http response that sent by the browser.
Let say a redirect to "http://domain/api/something", actually, a GET request to "http://domain/api/something" which return a JSON data. How can I get that data on first load using AngularJs?
You should modify your code as below
app.service('feedbackService', function ($http) {
this.getFeedbackPaged = function () {
return $http.get('http://domain/api/something');
};
});
app.controller('feedbackController', function ($scope, feedbackService, $filter) {
// Constructor for this controller
init();
function init() {
feedbackService.getFeedbackPaged().then(function(data){
$scope.feedbackItems=data;
});
}
});
Use $http service as follows.
$http.get(
'http://domain/api/something'
).then(function successCallback(response) {
$scope.data = JSON.parse(response.data);
}, function errorCallback(response) {
// error handler
});
reference: https://docs.angularjs.org/api/ng/service/$http

Restangular with JSONP

I'm trying to use JSONP with Restangular. I'm using the Songkick API and when making a GET request I'm receiving no response data from a different domain but locally I receive data no problem.
I've created the following Factory for configuring Restangular and a controller. I'm a little unsure about how to use setDefaultRequestParams. Maybe the configuration is incorrect?
angular
.module('ModuleName')
.factory('RestFactory', Factory);
function Factory (Restangular) {
var factory = {
songkick: songkick
};
return factory;
function songkick () {
return Restangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.setJsonp = true;
RestangularConfigurer.setDefaultRequestParams('jsonp', {
callback: 'JSON_CALLBACK'
});
RestangularConfigurer.setDefaultRequestParams('get', {
reason: 'attendance',
apikey: 'xxxxxxxxxx'
});
RestangularConfigurer.setBaseUrl('http://api.songkick.com/api/3.0/');
RestangularConfigurer.setRequestSuffix('.json');
RestangularConfigurer.setDefaultHttpFields({cache: true});
});
}
}
angular
.module('ModuleName')
.controller('UserController', Controller);
function Controller ($stateParams, RestFactory) {
var user = this;
activate();
function activate () {
RestFactory.songkick().one('users/'+$stateParams.username+'/calendar')
.get()
.catch(function(response){
console.log("Error with status code", response.status);
});
}
}
Did you miss the JSONP option settings for Restangular?
Something like the following:
//JSonp
RestangularProvider.setJsonp(true);
RestangularProvider.setDefaultRequestParams('jsonp', {callback: 'JSON_CALLBACK'});
Documented here

Circular dependency while pushing http interceptor

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

Stop request in angularjs interceptor

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');
}

Resources