AngularJS - $httpProvider Interceptor handling and modify response data - angularjs

I want to use an interceptor for status code error handling. For example, when I'm loading an template through $route and I don't have the permission to do so I'm sending an 403 status code back from the server together with some html data, which I want to display instead of the originally requested template. Is this possible inside my interceptor in the 'responseError' function?
'responseError': function(rejection) {
// do something on error EXAMPLE
if (canRecover(rejection.htmlData)) {
return responseOrNewPromise //here I want to add my server error html data and return that to the client instead of the originally requested data
}
return $q.reject(rejection);
}
I know, that I could do a location.path change but i want to save the extra routing.

From ngRoute docs:
$routeChangeError
Broadcasted if any of the resolve promises are rejected.
Add an event listener to when a promise resolve is rejected
app.run(function($rootScope){
$rootScope.$on('$routeChangeError',function(angularEvent,current,previous,rejection){
// console.log(rejection)
})
})
I also recommend these tutorials:
https://egghead.io/lessons/angularjs-resolve-routechangeerror
https://egghead.io/lessons/angularjs-directive-for-route-handling

Related

Angularjs route: prevent user to manually get json data

In my angularjs app I have route /#/tasks, which fetch tasks from /tasks url. If user manually goes to /tasks url, he gets json data. Should I prevent show json data to user? What is the best practice? For example, in backend I can check if request is ajax or not. If not - redirect to base url. Backend - laravel 5.
From the title of the post having the keywords 'prevent user', it seems that you need some sort of logic to define access privileges for a particular user.
You could stick to resolving the authorization logic before a user ever accesses a route.
For this, you can take advantage of the $routeProvider's resolve property of the .when() method.
According to the docs, the resolve property is ...
An optional map of dependencies which should be injected into the controller. If any of these dependencies are promises, the router will wait for them all to be resolved or one to be rejected before the controller is instantiated.
If all the promises are resolved successfully, the values of the resolved promises are injected and $routeChangeSuccess event is fired. If any of the promises are rejected the $routeChangeError event is fired.
The resolve takes in functions (as injectable dependencies) each of which may return a promise, and if any of the promises get rejected, as quoted, an error is thrown and literally, neither the route nor the view is loaded.
For the authorization logic to work, you'll need to perform some API call to some backend function/service which either resolves that the user is permitted for the route, or rejects his/her request.
.when('/tasks', {
resolve: {
authorize: function(authService){
//some api call to get either acceptance (e.g. status code 200) or rejection (status code 500) for the user being authorized
return authService.authorize();
}
},
controller: function(){
//route controller logic
}
})
Alternatively, if you'd also want to perform something on failure of the promise, you could do so by simply chaining the promise with a catch() handler and throw an error to forcefully reject the promise that is returned from the catch() itself.
.when('/tasks', {
resolve: {
authorize: function($location, authService){
//some api call to get either acceptance or rejection for the user being
return authService.authorize()
.then(function(){
//some logic on success of the promise
})
.catch(function(){
$location.path('/auth/login/');
throw 'Authorization error';
});
}
},
controller: function(){
//route controller logic
}
})

AngularJs watch for change in variable inside a service

I have a service named loginManager which stores objects called is_logged_in & api_token along with few others. My various controllers make ajax calls using $http using the api_token.
If the api_token is reset/expired on server, response is sent as auth_error, at this point I set is_logged_in = false
What i want to achieve is, whenever is_logged_in is changed, the service redirects to /login using $location.path('/login'), i.e. to say, I want to watch the object inside the service, and invoke callback on change from service itself.
I just want the service to take care of login and corresponding routing, without any controller worrying about weather user is logged in or not.
I believe Pankaj Pakar's answer could work but you should use angular's interceptors for that. They intercept all messages. You could add hook for response or responseError and when you recieve auth_error you do any action you like. For example $location.path('/login'), display error to user, etc.
If you want to separate logic you could inject your service with all code inside and just call some method on it.
I'd suggest you to put that watcher in run phase on the angular application which will be there at a single place, by which you could check the value is_logged_in flag of service & if user is not login then redirect him/her to login page directily.
Code
app.run(function($rootScope, loginManager, $location){
$rootScope.$watch(function(){
return loginManager.is_logged_in;
}, function(newValue){
if(angular.isDefine(newValue) && !newValue)
$location.path('/login');
//$state.go('login'); //if you are using ui.router
})
})
Edit
Really curious part of your question is, from where you are changing is_logged_in flag of your service as #JBNizet asked? If any code is there is JavaScript then you should directly redirect to login page from there.
I feel the need to answer something more, Mior is quite right, but his answer needs more meat.
Here I show you how I managed to handle ALL server XHR requests with response 401 unauthorized.
First of all you need a service:
'use strict';
angular.module('theModule')
.factory('interceptorService', ['$q', '$location', function ($q, $location) {
return {
response: function (response) {
return response || $q.when(response);
},
responseError: function (rejection) {
var returnTo = $location.path().replace(/^\/|\/$/g, '');
if (returnTo === 'login') {
return;
}
if (rejection.status === 401) {
console.log('Unauthorized');
$location.path('/login').search('returnTo', returnTo);
}
return $q.reject(rejection);
}
};
}]);
This will be used to intercept all XHR calls and to change the location every time a 401 error is found.
I've also added an improvement that is the "returnTo" parameter, you will be able to use it after login to return to the previous page.
To bind it to each request you have to call the config method, this is my main javascript.
'use strict';
/**
* #author Gianmarco Laggia
*
* Main module of the application an configurations.
*/
angular
.module('theModule', [])
.config(['$httpProvider', function ($httpProvider) {
//Http Interceptor to check auth failures for xhr requests
$httpProvider.interceptors.push('interceptorService');
}]);
This is pretty much what you need to intercept every request, working on the rejection.status you can also intercept events such as server down (status is -1), internal server error (500+), success status (in the response part, status 200+) etc.

Angularjs analogue of jQuery .always()

I want to make a HTTP request to my server and can't find the answer on my question how to make .always() like in jQuery style.
According Angular's documentation of $http, there is only this construction:
// Simple GET request example :
$http.get('/someUrl').
then(function(response) {
// this callback will be called asynchronously
// when the response is available
}, function(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
There's the finally():
$http.get('/someUrl').
then(function(response) {
// this callback will be called asynchronously
// when the response is available
}.catch(error) {
// called asynchronously if an error occurs
// or server returns response with an error status.
}).finally() {
//Always do something
});
Which will always be called no matter what the outcome is.
You can read about this in the documentation for $q here.

Right way to disable/remove http interceptors in Angular?

I followed this post to implement a similar ajax loader image on a project:
Implementing loading spinner using httpInterceptor and AngularJS 1.1.5
My implementation has a few differences:
I use $rootScope to emit and not broadcast and I also use $rootScope on the directive to handle the event.
Because of a particularity of the project, I have to unbind the directive $rootScope.$on listeners right after the first event being fired (either for show or hide), inside the event handler.
I only fire a single show/hide event. Show on the first HTTP request, hide when the count reaches 0.
I believe those are the major differences from the linked post, not sure if they are relevant to my question but just in case they are...
When the loader hide event is handled, the loader is gone and I won't show it again unless the page is refreshed, but I still have background http requests to refresh data on the current page. Those requests will still be intercepted and fire new show/hide events, which are no longer required/handled. I only need a first show and first hide, that's it.
What's the right way to remove the HTTP interceptor I added to the $httpProvider after the the first hide event has been fired?
I know we add the interceptor using a $httpProvider.interceptors.push() but I'm not sure how to pop it out when I no longer need that interceptor.
I was going to put a bounty on this, as I had the same question. However....it looks as though the interceptors and responseInterceptors are simply arrays, according to the source code (lines 127 and 133 in the $httpProvider factory). There's no wrapper around this.
From what I can tell, you would either have to use pop() or any other array method. However, this means that you don't know what you're popping! Holding a reference to the object wouldn't really help, because you can't really do an array function on it, unless you decide to iterate based on equality (which could work, using indexOf or something else like Underscore).
Really, what Angular needs is a wrapper for this since you can't be sure that your interceptor is the last one on the list.
The best solution that I've found is the one explained by jedd.ahyoung in his comment.
These are the steps.
Add two custom factories
angular.module('myModule.services', [])
/**
* Save application information.
*/
.factory('Application', function($log) {
return {};
})
/**
* Custom Http Interceptor for the loader.
*/
.factory('myHttpInterceptor', function($rootScope, $q, Application) {
return {
request: function(config) {
if(Application.enableLoader){
$rootScope.$broadcast('loading:show');
}
return config;
},
requestError: function(rejection) {
$rootScope.$broadcast('loading:hide');
return $q.reject(rejection);
},
response: function(response) {
$rootScope.$broadcast('loading:hide');
return response;
},
responseError: function(rejection) {
$rootScope.$broadcast('loading:hide');
return $q.reject(rejection);
}
};
});
Add it in your config step
.config(function($httpProvider) {
//loading interceptor
$httpProvider.interceptors.push('myHttpInterceptor');
});
Enable/disable it when/where you want
Application.enableLoader = true;
$http({
url: url,
method: "GET"
}).success(function(data){
$log.log("Data received and my loader is already closed :)");
Application.enableLoader = false;
$scope.retrieveBooks();
}).error(function(){
$log.log("Error, but my loader is already closed :)");
Application.enableLoader = false;
$scope.retrieveBooks();
});
$scope.retrieveBooks = function(){
$http({
url: url,
method: "GET"
}).success(function(data){
$log.log("Data received and my loader is not closed :(");
}).error(function(){
$log.log("Error, but my loader is not closed :(");
});
};

Angularjs resource status description

I have an issue with angularjs app communicating with a REST API. The problem is the api returnes a status code and also adds a status description like HTTP/1.1 417 Invalid quantity.
With jquery ajax the jqXHR object had a statusText property but with angularjs I can't seem to find how can I access this in my error handler. Needless to say I can't modify the API which for a 417 status code for example can return different status descriptions.
I have found that I would need to change the angularjs library but in case of an update this would not be handy. I would have to change xhr.responseText to xhr.statusText. Can I somehow overwrite this function from my code and don't modify angular library?
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
completeRequest(
callback, status || xhr.status, xhr.responseText, xhr.getAllResponseHeaders());
}
};
Here's the work around to get StatusText (as pointed out that some mobile browsers strip out DATA from successfull responses: https://stackoverflow.com/a/28470163/1586498
I have also discovered that .success and .error on $http in Angular does not pass the StatusText, you have to use .then(response)
You need to create an httpInterceptor as outlined here during your module config.
// register the interceptor as a service
$provide.factory('myHttpInterceptor', function($q) {
return function(promise) {
return promise.then(function(response) {
// do something on success
}, function(response) {
//do something for your 417 error
if(response.status == 417) {
alert("We've got a 417!");
return $q.reject(response);
}
//some other error
return $q.reject(response);
});
}
});
$httpProvider.responseInterceptors.push('myHttpInterceptor');
The support for statusText was added in AngularJS 1.2.16.

Resources