When my website was 100% jQuery, I used to do this:
$.ajaxSetup({
global: true,
error: function(xhr, status, err) {
if (xhr.status == 401) {
window.location = "./index.html";
}
}
});
to set a global handler for 401 errors. Now, I use angularjs with $resource and $http to do my (REST) requests to the server. Is there any way to similarly set a global error handler with angular?
I'm also building a website with angular and I came across this same obstacle for global 401 handling. I ended up using http interceptor when I came across this blog post. Maybe you'll find it as helpful as I did.
"Authentication in AngularJS (or similar) based application.", espeo software
EDIT: final solution
angular.module('myApp', ['myApp.filters', 'myApp.services', 'myApp.directives'], function ($routeProvider, $locationProvider, $httpProvider) {
var interceptor = ['$rootScope', '$q', function (scope, $q) {
function success(response) {
return response;
}
function error(response) {
var status = response.status;
if (status == 401) {
window.location = "./index.html";
return;
}
// otherwise
return $q.reject(response);
}
return function (promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);
Please note that responseInterceptors have been deprecated with Angular 1.1.4.
Below you can find an excerpt based on the official docs, showing the new way to implement interceptors.
$provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
return {
'response': function(response) {
// do something on success
return response || $q.when(response);
},
'responseError': function(rejection) {
// do something on error
if (canRecover(rejection)) {
return responseOrNewPromise;
}
return $q.reject(rejection);
}
};
});
$httpProvider.interceptors.push('myHttpInterceptor');
This is how it looks in my project using Coffeescript:
angular.module("globalErrors", ['appStateModule']).factory "myHttpInterceptor", ($q, $log, growl) ->
response: (response) ->
$log.debug "success with status #{response.status}"
response || $q.when response
responseError: (rejection) ->
$log.debug "error with status #{rejection.status} and data: #{rejection.data['message']}"
switch rejection.status
when 403
growl.addErrorMessage "You don't have the right to do this"
when 0
growl.addErrorMessage "No connection, internet is down?"
else
growl.addErrorMessage "#{rejection.data['message']}"
# do something on error
$q.reject rejection
.config ($provide, $httpProvider) ->
$httpProvider.interceptors.push('myHttpInterceptor')
Create the file <script type="text/javascript" src="../js/config/httpInterceptor.js" ></script> with this content:
(function(){
var httpInterceptor = function ($provide, $httpProvider) {
$provide.factory('httpInterceptor', function ($q) {
return {
response: function (response) {
return response || $q.when(response);
},
responseError: function (rejection) {
if(rejection.status === 401) {
// you are not autorized
}
return $q.reject(rejection);
}
};
});
$httpProvider.interceptors.push('httpInterceptor');
};
angular.module("myModule").config(httpInterceptor);
}());
Related
I want to check if user is online before making call using $http in angular app, as a fallback I will get the cached data if network is not available.
Is there any option like before callback in $http do run this check?
Or maybe any other way to tackle this, I have network state & cache in localstorage
You could just write your own http service wrapper.
function httpMonkey ($http) { // I like to call all my services 'monkeys'; I find it makes angular more fun
function request (args) {
// stuff to do before, likely as a promise
.then(function () {
// the actual http request using $http
})
.then(function () {
// stuff to do after, perhaps?
});
}
var service = { request: request };
return service;
}
angular
.module('example')
.factory('HttpMonkey', httpMonkey);
You can add a custom httpInteceptor to the $httpProvider service in angularJs. As an example below - I have created an httpInteceptor which will show loadingSpinner before each $http call and hide it after success/error.
//Intercepts ALL angular ajax http calls
app.factory('httpInterceptor', function ($q, $rootScope, $log) {
var numLoadings = 0;
return {
request: function (config) {
numLoadings++;
// Show loader
$('#loadingSpinner').show();
return config || $q.when(config)
},
response: function (response) {
if ((--numLoadings) === 0) {
// Hide loader
$('#loadingSpinner').hide();
}
return response || $q.when(response);
},
responseError: function (response) {
if (!(--numLoadings)) {
// Hide loader
$('#loadingSpinner').hide();
}
return $q.reject(response);
}
};
})
and then push this interceptor to the $httpProvider.interceptors in your app.config-
app.config(function ($routeProvider, $httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
.
.
});
My $http functions can return the following errors:
POST http://foobar.dev/foobar 500 (Internal Server Error)
POST http://foobar.dev/foobar 401 (Unauthorized)
Isn't there a way I can catch all status codes?
$http.post('/foobar', form)
.success(function(data, status, headers, config) {
console.info(data);
})
.error(function(data, status, headers, config) {
console.error(data);
if(status === 401) {
$scope.setTemplate('show-login');
}
if(status === 500) {
$scope.setTemplate('server-error');
}
}
);
Where $scope.setTemplate() is a function inside the Controller that sets a view.
But then I have to do this for each error() function and there are a lot functions like this which also not making it DRY code :P
What I want is to catch the error and do an action based on the status code returned in the error.
FYI: I'm not using Angulars $routeProvider().
You can use the Angular $http interceptor for this like #Dalorzo explained:
var myApp = angular.module("myApp", [], ['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(['$rootScope', '$q', function($rootScope, $q) {
return {
'responseError': function(response) {
var status = response.status;
// Skip for response with code 422 (as asked in the comment)
if (status != 422) {
var routes = {'401': 'show-login', '500': 'server-error'};
$rootScope.$broadcast("ajaxError", {template: routes[status]});
}
return $q.reject(response);
}
};
}]);
});
Then receive it in your controller:
$scope.$on("ajaxError", function(e, data) {
$scope.setTemplate(data.template);
});
Now, you don't have to put in your each error function.
How about something like this instead:
var routes = {'401':'show-login', '500': 'server-error'};
$scope.setTemplate(routes[status]);
Where routes is a dictionary with your error codes and desired routing.
This is exactly what $http interceptors are for. See the interceptors section here: $http
Basically, you create common functionality for all $http requests, in which you can handle different statuses. For example:
// register the interceptor as a service
$provide.factory('myHttpInterceptor', function($q, dependency1, dependency2){
return {
response: function(response){
// do something for particular error codes
if(response.status === 500){
// do what you want here
}
return response;
}
};
});
// add the interceptor to the stack
$httpProvider.interceptors.push('myHttpInterceptor');
What I would say initially is to create a decorator for the $http service or create a service that would serve as a wrapper for the $http service.
In AngularJS, I wish to create a catch-all-be-all ajax loader that does not need to be weaved into each controller in order to work. Traditionally, in jQuery I can do something like this:
(function globalAjaxLoader($){
"use strict";
var ajaxBoundElements = [$posts, $navigationLinks];
ajaxBoundElements.forEach(function($elm){
$elm.on('click', function(){
$loader.show();
});
$(document).ajaxSuccess(function (event, XMLHttpRequest, ajaxOptions){
$loader.hide();
});
})(jQuery);
However, in AngularJS I am not seeing a global way of detecting ajaxCompletion (that is without going through the promise returned for each ajax call made through Angular individually)?
Thanks.
Here I've put together a jsBin showing how to do this with an http interceptor.
I've used $rootScope.loadingCount so you can actually ng-show and ng-hide your based on that. Here is an example of the markup (you'd obviously use something a bit different:
<h1 ng-show="loadingCount > 0">Loading...</h1>
And here is the javascript:
angular
.module('app', [])
.config(httpInterceptorConfig)
.factory('loadingDialogInterceptor', loadingDialogInterceptor);
// create your interceptor
loadingDialogInterceptor.$inject = ['$q', '$rootScope'];
function loadingDialogInterceptor($q, $rootScope) {
$rootScope.loadingCount = 0;
function showLoading() {
$rootScope.loadingCount++;
}
function hideLoading() {
if ($rootScope.loadingCount > 0) {
$rootScope.loadingCount--;
}
}
return {
request: function (config) {
showLoading();
return config || $q.when(config);
},
response: function(response) {
hideLoading();
return response || $q.when(response);
},
responseError: function(response) {
hideLoading();
return $q.reject(response);
}
};
}
// actually register your interceptor
httpInterceptorConfig.$inject = ['$httpProvider'];
function httpInterceptorConfig($httpProvider) {
$httpProvider.interceptors.push('loadingDialogInterceptor');
}
You can achieve the same with interceptors.
myModule
.factory('httpResponseInterceptor', [
'$q',
function ($q) {
return {
request: function (request) {
$loader.show();
return request;
},
requestError: function () {
$loader.hide();
},
response: function (response) {
$loader.hide();
return response || $q.when(response);
},
responseError: function (rejection) {
$loader.hide();
return $q.reject(rejection);
}
}
}
])
.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push('httpResponseInterceptor');
$httpProvider.defaults.transformRequest.push(function (data) {
$loader.show();
return data;
});
})
I am making a simple get request using $http .and it fails with 503-service unavailable error which is clearly shown in the network tab .But the rejection object in the responseError interceptor object shows status: 0.
Here is my Interceptor
angular.module("app", []).config(function($httpProvider){
$httpProvider.interceptors.push(function($q) {
return {
'responseError': function(rejection) {
console.log(rejection);
}
}
})
});
I am expecting status code 503 in the interceptor but i am getting 0.Please help me to understand and resolve the issue.
Here is a fiddle illustrating the issue.
The request is a cross domain request.
The service you are using does return any data. Angular $http expects some data to be returned, and in this case "GetStatusCode" just stops and does not return anything.
Also, fyi - your fiddle does not respect angular's method of binding to a controller, which expects a string for the controller name.
See http://plnkr.co/edit/x46D5FIsgaKsaSocYjpz?p=preview for proper markup.. where we name the controller as a string, for ex:
angular.module("plunker", [])
.config(function ($httpProvider) {
$httpProvider.interceptors.push(function () {
return {
'responseError': function (rejection) {
console.log('rejection = ', rejection);
},
'response': function (response) {
console.log('response = ', response);
}
}
})
})
.controller('Controller', function Controller($scope, $http) {
$scope.getCode = function () {
var req = 'http://www.reddit.com/r/catpictures.json?limit=50&jsonp=JSON_CALLBACK';
return $http.jsonp(req);
};
$scope.get = function () {
return $scope.getCode()
};
})
;
I am Getting this Error for unknown reasons while trying to implement a AJAX Spinner loading code.
I don't understand where the header should be defined. I did console.log(config) but I can see headers: accept: text/html value there.
Below is my Code:
/**
* Spinner Service
*/
//Spinner Constants
diary.constant('START_REQUEST','START_REQUEST');
diary.constant('END_REQUEST','END_REQUEST');
//Register the interceptor service
diary.factory('ajaxInterceptor', ['$injector','START_REQUEST', 'END_REQUEST', function ($injector, START_REQUEST, END_REQUEST) {
var $http,
$rootScope,
myAjaxInterceptor = {
request: function (config) {
$http = $http || $injector.get('$http');
if ($http.pendingRequests.length < 1) {
console.log(config);
$rootScope = $rootScope || $injector.get('$rootScope');
$rootScope.$broadcast(START_REQUEST);
}
}
};
return myAjaxInterceptor;
}]);
diary.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push('ajaxInterceptor');
}]);
I think I have the solution.
I've had the same problem under an AngularJS project where an interceptor is exactly defined the same as yours (https://docs.angularjs.org/api/ng/service/$http#interceptors)
To shorten, an interceptor catch the config and have to return it. And you forgot to.
So that would be:
request: function (config) {
$http = $http || $injector.get('$http');
if ($http.pendingRequests.length < 1) {
$rootScope = $rootScope || $injector.get('$rootScope');
$rootScope.$broadcast(START_REQUEST);
}
return config;
}
Here you have a full sample about how to implement a spinner using interceptors (wrapping the $rootScope in a service for better code readibility).
http://lemoncode.net/2013/07/31/angularjs-found-great-solution-to-display-ajax-spinner-loading-widget/
As you pointed out, this is deprecated (I have to update the post), the current structure I'm using (simplified inner code). I think the best could be to start from a plunker, maybe it has nothing to do with the way tou are implementing (let me search for a seed plunkr)
myapp.factory('httpInterceptor', ['$q', '$injector',
function ($q, $injector) {
return {
'request': function(config) {
// request your $rootscope messaging should be here?
return config;
},
'requestError': function(rejection) {
// request error your $rootscope messagin should be here?
return $q.reject(rejection);
},
'response': function(response) {
// response your $rootscope messagin should be here?
return response;
},
'responseError': function(rejection) {
// response error your $rootscope messagin should be here?
return $q.reject(rejection);
}
};
}
]);