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.
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');
.
.
});
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()
};
})
;
In the code below, I'd like handling errors :
401 : redirect to a login page
other : display error message (received in the message of the error)
I don't find the right way to do this.
Any idea ?
Thanks,
Module.js
var app;
(function () {
app = angular.module("studentModule", []);
})()
Service.js
app.service('StudentService', function ($http) {
this.getAllStudent = function () {
return $http.get("http://myserver/api/Student/");
}
});
Controller.js
app.controller('studentController', function ($scope, StudentService) {
function GetAllRecords() {
var promiseGet = StudentService.getAllStudent();
promiseGet.then(function (pl) { $scope.Students = pl.data },
function (errorPl) {
$log.error('Some Error in Getting Records.', errorPl);
});
}
});
As with most problems, there are many different ways to handle errors from AJAX requests in AngularJS. The easiest is to use an HTTP interceptor as already pointed out. This can handle both authentication and errors.
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(['$rootScope', '$q', function($rootScope, $q) {
return {
responseError: function(rejection) {
var deferred;
// If rejection is due to user not being authenticated
if ( rejection.status === 401 ) {
$rootScope.$broadcast('unauthenticated', rejection);
// Return a new promise since this error can be recovered
// from, like redirecting to login page. The rejection and
// and promise could potentially be stored to be re-run
// after user is authenticated.
deferred = $q.defer();
return deferred.promise;
}
$rootScope.$broadcast('serverError', rejection);
// Just reject since this probably isn't recoverable from
return $q.reject(rejection);
}
}
};
}]);
The above interceptor is created using an anonymous function but factories can be used to handle one or many different interceptors. The AngularJS docs have decent information about how to write different ones: https://docs.angularjs.org/api/ng/service/$http#interceptors
With the interceptors in place, you now just need to listen from the broadcasted events in your run method or any controller.
app.run(['$rootScope', '$location', function($rootScope, $location) {
$rootScope.$on('unauthenticated', function(response) {
// Redirect to login page
$location.path('/login');
});
$rootScope.$on('serverError', function(response) {
// Show alert or something to give feedback to user that
// server error was encountered and they need to retry
// or just display this somewhere on the page
$rootScope.serverError = response.data.errorMessage;
});
}]);
In your view:
<body ng-app="studentModule">
<div id="server_error" ng-if="!!serverError">{{serverError}}</div>
...rest of your page
</body>
Like almost all AngularJS code, most of this can be abstracted into different factories and services but this should be a good place to start.
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);
}
};
}
]);
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);
}());