I am using the following to show a loading screen whenever I am performing a http request however sometimes if there is an error then it will stay loading (because of the backdrop the app becomes unusable). Rather than hide it on every error checker I was wondering if it is possible to call the timeout after 5 seconds?
.config(function($httpProvider) {
$httpProvider.defaults.timeout = 5000;
$httpProvider.interceptors.push(function($rootScope) {
return {
request: function(config) {
$rootScope.$broadcast('loading:show')
return config
},
response: function(response) {
$rootScope.$broadcast('loading:hide')
return response
}
}
})
})
Following Jess's answer it now looks like this :
.config(function($httpProvider) {
$httpProvider.defaults.timeout = 5000;
$httpProvider.interceptors.push(function($rootScope) {
return {
request: function(config) {
$rootScope.$broadcast('loading:show')
return config
},
response: function(response) {
$rootScope.$broadcast('loading:hide')
return response
},
responseError: function(response) {
$rootScope.$broadcast('loading:hide')
return response
},
requestError: function(response) {
$rootScope.$broadcast('loading:hide')
return response
}
}
})
})
However I cannot seem to be able to put an alert in the requestError to inform the user.
Question
How can I implement an alert to notify the user of the error that has occurred?
try adding responseError and requestError so like this:
responseError: function(responseError) {
$rootScope.$broadcast('loading:hide')
return responseError
and do this again with requestError,
This is from the angular http interceptors docs
requestError: interceptor gets called when a previous interceptor threw an error or resolved with a rejection.
responseError: interceptor gets called when a previous interceptor threw an error or resolved with a rejection.
Edit to answer comment:
so if you want to throw a alert on responseError than a add a $rootScope.$broadcast('response:error')
in the responseError function
then in the controller you want to throw the alert in just do a
$scope.$on('response:error', function(){throw the error here});
you can also do the same for requestError
this works because $broadcast -- dispatches the event downwards to all child scopes
Related
I'm building an app with ionic and I'm showing a loading indicator on every request that loads. When the request finishes, the loading indicator is removed. This works great when a network connection is present.
When offline, the request starts and the loading indicator is show. Problem is that the loading indicator never disappears because the request never finishes. How can I best handle this? I'm thinking about a timeout or something. The best way would be not to start the request at all when a user is offline.
$httpProvider.interceptors.push(function($rootScope) {
return {
request: function(config) {
config.timeout = 5000;
$rootScope.$broadcast('loading:show')
return config
},
response: function(response) {
$rootScope.$broadcast('loading:hide')
return response
}
}
})
add below
responseError: function(response) {
$rootScope.$broadcast('loading:hide')
return response
}
requestError: function(response) {
$rootScope.$broadcast('loading:hide')
return response
}
I have created http interceptors and attached to my main app as below:
angular.module('app',['ui.router'])
.factory('AuthInterceptor',function($window,$q){
return{
request: function(config){
console.log("config object is");
console.log(config);
if($window.sessionStorage.getItem('token')){
config.headers['x-access-token']= $window.sessionStorage.getItem('token');
}
return config || $q.when(config);
},
response: function(response){
console.log("response object is:");
console.log(response);
if (response['status'] >= 400) {
console.log("Not Authorized.kindly login first");
$state.transitionTo('login');
}
return response || $q.when(response);
}
};
})
.config(function($httpProvider){
$httpProvider.interceptors.push('AuthInterceptor');
});
On the server-side (some express code) I am checking if the user is authorized or not and if not I respond with the following code (only displaying a subset of code to keep things concise):
if(!req.user){
res.send('Not authorized',400);
}
The server code works fine (i can see it in the network tab of the chrome developer tools)
However AuthInterceptor.response() does nto get called.
However do note that the AuthInterceptor.response() does get executed when the response status is 200
So I am confused.. why is it not intercepting 400 statuses ?
If the response status code is outside of the range [200-299], then the responseError interceptor is called.
You need to provide a responseError interceptor:
return {
request: function (config) {...},
responseError: function (rejection) {...}
};
Sometimes, the API I'm using will return 200 ok even though there has been an error. The response JSON object will look something like:
{
error: true
}
I've built a $http response interceptor that simply checks for this error and rejects it. I want it to then jump into my responseError function:
$httpProvider.interceptors.push(function($q) {
return {
response: function (response) {
if (response.data.error) {
// There has been an error, reject this response
return $q.reject(response);
}
return response;
},
responseError: function(rejection) {
// Display an error message to the user
...
return $q.reject(rejection);
}
}
});
Problem is, even after rejecting the response, my responseError function isn't called. It is called on 500 errors etc so I know it's working. I'd expect a rejection to do the same thing.
From the docs:
responseError: interceptor gets called when a previous interceptor threw an error or resolved with a rejection.
Any ideas on what's missing?
Looks like this isn't possible to do. To cut down on duplicate code, simply declare the error handling function separately and reuse it inside the response and responseError functions.
$httpProvider.interceptors.push(function($q) {
var handleError = function (rejection) { ... }
return {
response: function (response) {
if (response.data.error) {
return handleError(response);
}
return response;
},
responseError: handleError
}
});
To add to this answer: rejecting the promise in the response interceptor DOES do something.
Although one would expect it to call the responseError in first glance, this would not make a lot of sense: the request is fulfilled with succes.
But rejecting it in the response interceptor will make the caller of the promise go into error handling.
So when doing this
$http.get('some_url')
.then(succes)
.catch(err)
Rejecting the promise will call the catch function. So you don't have you proper generic error handling, but your promise IS rejected, and that's useful :-)
Should you want to pass the http response to the responseError handler, you could do it like this:
$httpProvider.interceptors.push(function($q) {
var self = {
response: function (response) {
if (response.data.error) {
return self.responseError(response);
}
return response;
},
responseError: function(response) {
// ... do things with the response
}
}
return self;
});
According to http://docs.angularjs.org/api/ng.$http an interceptor has following methods:
request: function(config) {
//we could manipulate query here
return config || $q.when(config);
},
requestError: function(rejection) {
// what is the use case of this?
return $q.reject(rejection);
},
response: function(response) {
// response.status === 200
return response || $q.when(response);
},
responseError: function(rejection) {
// when response failed ...
return $q.reject(rejection);
}
How is requestError triggered and what use-cases can you think of?
One interesting use case is to tear down / undo things that have been set up before a request, and would have been teared down after a response. Examples:
loading indicator
overlay
disabled form fields
Now when the request can't be sent or is rejected by another interceptor, requestError gives you the chance to act appropriately and remove that loading indicator or enable the form fields.
So I have a bunch of controllers that do $http requests
but in every $http request i have a .error(function(data...){//always the same})
How could I build an.. "abstract class" for $http?
This here would be the always repeating code
.error(function(){
$scope.flashes = {
server: {
type: "danger",
message: "There was a server error processing your request. Please try again later."
}
};
})
I add the same concern few weeks ago and i came up with this solution :
I first created a custom service intercepting every http requests made :
.factory('HttpInterceptor', ['$q', '$rootScope', function($q, $rootScope) {
return {
// On request success
request : function(config) {
// Return the config or wrap it in a promise if blank.
return config || $q.when(config);
},
// On request failure
requestError : function(rejection) {
//console.log(rejection); // Contains the data about the error on the request.
// Return the promise rejection.
return $q.reject(rejection);
},
// On response success
response : function(response) {
//console.log(response); // Contains the data from the response.
// Return the response or promise.
return response || $q.when(response);
},
// On response failure
responseError : function(rejection) {
//console.log(rejection); // Contains the data about the error.
//Check whether the intercept param is set in the config array. If the intercept param is missing or set to true, we display a modal containing the error
if (rejection.config && typeof rejection.config.intercept === 'undefined' || rejection.config.intercept)
{
//emitting an event to draw a modal using angular bootstrap
$rootScope.$emit('errorModal', rejection.data);
}
// Return the promise rejection.
return $q.reject(rejection);
}
};
}]);
I also defined a custom config property 'intercept' that i can add to the $http config object. It is useful when I don't want to apply this behavior on a particular request.
E.g :
var registerResource = $resource('/registration/candidate/register', {}, {query:
{method:'POST', isArray: false, intercept: false }
});
In order the have a flexible solution, it is also important to not forget to do :
return $q.reject(rejection);
So you can still use the error callback on your promise in your controller if you want to combine both ways (interception + manual handling)
Finally, I added this service to my application :
app.config(['$httpProvider', function($httpProvider) {
// Add the interceptor to the $httpProvider to intercept http calls
$httpProvider.interceptors.push('HttpInterceptor');
}]);
I simplified the service but you can also use it for many things. Personally, I also use it to :
Make sure to not fire duplicate http requests (if the user click a lot on a submit button).
Draw an alert at the beginning of an http call and close it at the end to inform the user that is treatment is processing (export of data for instance).
PS: The official documentation mention this interceptor
You could do something like this:
app.service('myHttp', function($http){
return function($scope, httpParameters){
var httpPromise = $http(httpParameters);
httpPromise.error(function(){
$scope.flashes = {
server: {
type: "danger",
message: "There was a server error"
}
}
});
};
});
app.controller('MainCtrl', function($scope, myHttp) {
myHttp($scope, {method: 'GET', url: 'www.google.com'});
});