AngularJS: DRY $http .error() - angularjs

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

Related

ionic http request loading timeout

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

AngularJs : event listener on http requests

Since i'm using Oauth2 to protect my Api, i need to get a new access token before any http requets if the previous access token has expired.
I didn't used event listener much until now.
Here what i did for now (Please let me know if it is correct) :
ApplicationController.js :
app.controller('ApplicationController', function($rootScope, $scope, $localStorage, AuthService){
// Listening event apiRequested
$scope.$on('event:apiRequested', function(e) {
AuthService.token();
// Restore the access_token in case it has changed
access_token = $localStorage.getObject('access_token');
});
})
UserController.js :
$rootScope.$broadcast('event:apiRequested');
// Get Users around
return $http.post(domain+'/api/users?access_token='+access_token.key, data).then(function(response){
return response;
});
First thing i'm not sure about ... Does $http is processed if the event already executed entirely?
So since i'm not sure, i'm thinking about adding a callback.
Here the idea :
$rootScope.$broadcast('event:apiRequested', function(response){
if(response){
// Get Users around
return $http.post(domain+'/api/users?access_token='+access_token.key, data).then(function(response){
return response;
});
}
});
Please let me know if it is possible to do that or should i use something else than event listener for that case.
Why don't you use interceptors that is done to intercept HTTP request ?
In your case, you shall add this very specific behaviour into the "request" part.
See an interceptor exemple bellow:
var $myService; // Add a constant that store the service
$httpProvider.interceptors.push(['$location', '$injector', '$q', function($location, $injector, $q) {
return {
'request' : function(config){
console.log("intercept request", config.url,config)
// Your token shall be retreive in this part
return config
},
'response' : function(config){
$myService= $myService|| $injector.get('$myService'); // inject the service manually if constant is undefined
console.log("intercept response", config)
// Your token shall be retreive in this part
return config
},
'responseError': function(rejection) {
console.log("responseError intercepted" , rejection);
if (rejection.status === 403) {
return $q.reject(rejection);
} else if (rejection.status === 423) {
return $q.reject(rejection);
}else
return $q.reject(rejection);
}
};
}]);
Interceptors shall be defined into .config(["$httpProvider", function($httpProvider)

Angular : intercept specific request with $resource

I'm new to Angular, and am working on an interceptor. I created an angular factory to get some data from an API like that :
app.factory('Connection',['$resource',function($resource) {
return $resource('url',{param1: '1',param2: '55'},);
}]);
I also created the interceptor which looks like that :
app.factory('connectionInterceptor', function($q,$location) {
var connectionInterceptor = {
response: // code here
responseError: // code here
};
return connectionInterceptor;
});
The interceptor works well. But it intercepts every http request I do, and I'd like to make it work for a specific $resource. I read in angular $resource doc that there is a way to make it by adding an interceptor action/param to $resource. So I tried :
app.factory('Connection',['$resource',function($resource) {
return $resource('http://localhost:8080/api/login',{user: '1',password: '55'}, {},
query: {
method : 'GET',
interceptor : 'connectionInterceptor'
}
});
}]);
which didn't work. The thrown error is : Error in resource configuration for action query. Expected response to contain an object but got an array.
What did I miss ?
As you said, interceptors are globally set. I had to add a test to my response to check the $resource URL and add some specific treatment.
module.factory('interceptor', function() {
var interceptor = {
response: function(response) {
if (response.config.url.startsWith('my url')) {
// some treatment
}
else
// other treatment
return response;
}
return connectionInterceptor;
});

In angular, how can I catch the moment when an ajax request is successfully sent?

With angular $resource, I would like to fire a callback function when a request is successfully sent to the restful backend. (The backend may take a long time and I only want to know if it received the data I sent.)
The only thing I've found so far is resource.action.$promise['finally'](callback);
I'd be also interested to know when the request could not be sent. (eg. connection problems)
Thanks!
Here is DRY approach :
Build a service intercepting every HTTP requests (like the one defined in the official documentation) :
$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);
}
};
});
Simply put your code inside the desired hooks. You could for instance draw an error modal dialog if the request fails.
Finally, Register it to your application :
app.config(['$httpProvider', function($httpProvider) {
// Add the interceptor to the $httpProvider to intercept http calls
$httpProvider.interceptors.push('myHttpInterceptor');
}]);

Angularjs - handling 401's for entire app

I have the following code in one of my Controllers to handle a 401 gracefully:
ChannelsService.query(function(response) {
$scope.channels = response;
}, function(error) {
if (error.status == 401) {
$state.go('login');
}
});
and my corresponding service:
myServices.factory('ChannelsService', function ($resource) {
return $resource('/channels', {}, {
query: { method: 'GET', isArray: true },
create: { method: 'POST' }
})
});
I would like to know how to handle 401's globally so that I don't have to work this logic into every controller. Is it an interceptor that I need and if so could someone share some code?
Thanks
For purposes of global error handling, authentication, or any kind of synchronous or asynchronous pre-processing of request or postprocessing of responses, it is desirable to be able to intercept requests before they are handed to the server and responses before they are handed over to the application code that initiated these requests. The interceptors leverage the promise APIs to fulfill this need for both synchronous and asynchronous pre-processing.
You can add an interceptor to the $httpProvider when configuring your application
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(function($q) {
return {
'responseError': function(rejection){
var defer = $q.defer();
if(rejection.status == 401){
console.dir(rejection);
}
defer.reject(rejection);
return defer.promise;
}
};
});
}]);
As the name already suggests, this will intercept each request and call the provided function if there is a responseError (You could add interceptors for succeeded requests, too)
For further information, see the $http docs

Resources