My question is what is the best way to handle errors from http REST calls. Should I use interceptors or decorators? My rest functions look something like this:
queryFunction : function (config) {
var defer = $q.defer();
var config = {};
$http.get(someUrl, config) //or http.put, delete
.then(function (response) {
defer.resolve(response.data);
})
.catch(function (ex) {
defer.reject(ex);
});
return defer.promise;
},
How will the simplest interceptor look?
Here's the code for generic $http ErrorInterceptor:
app.factory('ErrorInterceptor', ['$q', function($q) {
return {
responseError: function(rejection) {
// An error message is shown for all kind of server errors
if (rejection.status == -1) {
//alert('Backend is not working');
//use rejection.data variable
}
return $q.reject(rejection);
}
};
}])
Then it can be included into app config
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('ErrorInterceptor');
})
Related
angular 1.6.1 has removed .success and .error in service $http for use .then and .catch.
Now my answer is:
It makes sense that I have written in my code? because now I'm confused on how the controller talks to the service
I have a controller for login (example):
LoginCTRL
User.login(data).then(
function(response){
//do something
},function(err){
//do somethig
});
And I have User service:
myapp.service('User', function User($q, $http) {
return {
login: function(credentials){
return $http.post('login',{data:data}).then(function onSuccess(response) {
return response;
}).catch(function onError(response){
return response;
});
},
}
It's correct write service.method().then in controller, now that there is no more $q.defer().resolve(data) or $q.defer().reject(reason)
try:
myapp.service('User', function User($q, $http) {
return {
login: function(credentials) {
var deferred = $q.defer();
$http.post('login', {
data: data
}).then(onSuccess, onFailure);
function onSuccess(response) {
deferred.resolve(response);
}
function onFailure(response) {
deferred.reject(response);
}
return deferred.promise;
}
}
}
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 ...
Is it possible to set a default action for $http in my app?
The idea would be that if any $http call in a controller, or service of my app would return 405, I could change to state 'login'
Something like this (of course this will not work at all, but I guess that shows the idea)
angular.module('app',[]).config(function($http){
$http.error(function(error,state){
if (state===405){
do stuff
}
);
You can use interceptors to achieve this.
// register the interceptor as a service
$provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
return {
// optional method
'request': function(config) {
// do something on success
return config;
},
// optional method
'requestError': function(rejection) {
// do something on error
if (canRecover(rejection)) {
return responseOrNewPromise
}
return $q.reject(rejection);
},
// optional method
'response': function(response) {
// do something on success
return response;
},
// optional method
'responseError': function(rejection) {
// do something on error
if (canRecover(rejection)) {
return responseOrNewPromise
}
return $q.reject(rejection);
}
};
});
$httpProvider.interceptors.push('myHttpInterceptor');
// alternatively, register the interceptor via an anonymous factory
$httpProvider.interceptors.push(function($q, dependency1, dependency2) {
return {
'request': function(config) {
// same as above
},
'response': function(response) {
// same as above
}
};
});
Read more about it here
You should do something like this:
$http({
method: 'GET',
url: '/someUrl'
}).then(function successCallback(response) {
//Do whatever you want on success
}, function errorCallback(response) {
if(response.status == 405) {
//Go to login
}
});
Below code might help you:
$http.get("url")
.success(function (data, status, headers, config) {
$scope.content = data;
if (data == '405') {
$state.go('login', {id: the_necesarry_id});
}
else {window.alert(' ');}
})
.error(function (data, status, headers, config) {
});
use angularjs Interceptors
// interceptors.js
.factory('httpInterceptor', function ($q, $injector) {
return {
'requestError': function(rejection) {
// do something on error
if(rejection.status === 405){
$injector.get('$state').transitionTo('login');
}
return $q.reject(rejection);
},
'responseError': function(rejection) {
// do something on error
if(rejection.status === 405){
$injector.get('$state').transitionTo('login');
}
return $q.reject(rejection);
}
};
});
// app.js
app.config( function ($httpProvider, $interpolateProvider, $routeProvider) {
$httpProvider.interceptors.push('httpInterceptor');
...
});
ExpressJS is sending the following response...
res.send('ItemUploaded');
I'm trying to get AngularJS to see this response via an Interceptor and perform a redirect. Does anyone have sample code where Angular catches a server response (such as my "ItemUploaded") and performs a redirect to a partial (via $location)?
This works fine. I have used it in my application.
var interceptor = function ($q, $location) {
return {
request: function (config) {//req
console.log(config);
return config;
},
response: function (result) {//res
console.log('Repos:');
console.log(result.status);
return result;
},
responseError: function (rejection) {//error
console.log('Failed with', rejection.status, 'status');
if (rejection.status == 403) {
$location.url('/dashboard');
}
return $q.reject(rejection);
}
}
};
module.config(function ($httpProvider) {
$httpProvider.interceptors.push(interceptor);
});
Here is the factory for the interceptor:
.factory('InterceptorService',['$q', '$location', function( $q, $location, $http){
var InterceptorServiceFactory = {};
var _request = function(config){
//success logic here
return config;
}
var _responseError = function(rejection) {
//error here. for example server respond with 401
return $q.reject(rejection);
}
InterceptorServiceFactory.request = _request;
InterceptorServiceFactory.responseError = _responseError;
return InterceptorServiceFactory;
}]);
then register the interceptor:
.config(["$httpProvider", function ($httpProvider) {
$httpProvider.interceptors.push('InterceptorService');
}]);
Every request coming will be passed here.
You can implement a interceptor factory which will redirect if it gets a matching result.
angular
.module('app')
.factory("httpinterceptor", ["$location",
function(location) {
return {
'response': function(response) {
if (response.data === "ItemUploaded") {
location.path("/ItemUploaded")
}
}
}
}
]);
I'd like to implement authentication on a single page web app with Angular.js. The official Angular documentation recommends the using of interceptors:
$provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
return {
// ...
'responseError': function(rejection) {
// do something on error
if (canRecover(rejection)) {
return responseOrNewPromise
}
return $q.reject(rejection);
}
};
});
The problem is when the server sends 401 error, the browser immediately stops with "Unauthorized" message, or with login pop-up window (when authentication HTTP header is sent by the server), but Angular can't capture with it's interceptor the HTTP error to handle, as recommended. Am I misunderstanding something? I tried more examples found on web (this, this and this for example), but none of them worked.
For AngularJS >1.3 use $httpProvider.interceptors.push('myHttpInterceptor');
.service('authInterceptor', function($q) {
var service = this;
service.responseError = function(response) {
if (response.status == 401){
window.location = "/login";
}
return $q.reject(response);
};
})
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('authInterceptor');
}])
in app config block:
var interceptor = ['$rootScope', '$q', "Base64", function(scope, $q, Base64) {
function success(response) {
return response;
}
function error(response) {
var status = response.status;
if (status == 401) {
//AuthFactory.clearUser();
window.location = "/account/login?redirectUrl=" + Base64.encode(document.URL);
return;
}
// otherwise
return $q.reject(response);
}
return function(promise) {
return promise.then(success, error);
}
}];
I don't know why, but response with 401 error goes into success function.
'responseError': function(rejection)
{
// do something on error
if (rejection.status == 401)
{
$rootScope.signOut();
}
return $q.reject(rejection);
},
'response': function (response) {
// do something on error
if (response.status == 401) {
$rootScope.signOut();
};
return response || $q.when(response);
}
AngularJS interceptors only work for calls made with the $http service; if you navigate to a page that returns a 401, AngularJS never even runs.