82

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?

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
cricardol
  • 4,212
  • 3
  • 20
  • 28
  • Is it a possible duplicate of [AngularJS Failed Resource GET](http://stackoverflow.com/questions/11598097/angularjs-failed-resource-get)? – MilkyWayJoe Aug 15 '12 at 14:36
  • 1
    No, we want to do a global error 401 handler for the application – cricardol Aug 15 '12 at 14:44
  • lol, have you considered that what you want but with a different http status (which you can change)? Anyhow, pkozlowski.opensource's answer shows you how to do it – MilkyWayJoe Aug 15 '12 at 15:39
  • No, it is a lot more like the answer of Justen...this is not a duplicate with the question you're talking – cricardol Aug 15 '12 at 16:51

3 Answers3

97

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);
Mark Rogers
  • 96,497
  • 18
  • 85
  • 138
Justen
  • 4,859
  • 9
  • 44
  • 68
  • It look great! I'll try it this afternoon – cricardol Aug 15 '12 at 15:47
  • I noticed at the bottom you begin the `$routeProvider.when(` function. Usually, that's in a `.config(`. Can this / should this exist outside the `.config(`? – drew schmaltz Jan 29 '13 at 01:00
  • I believe it can be placed outside the .config(), but that's where I store all my .whens() – Justen Jan 29 '13 at 17:51
  • Note that on the [docs for $http](http://docs.angularjs.org/api/ng.$http#responseinterceptors) they suggest creating a servicerather than doing it inline as above – Korny May 23 '13 at 01:52
  • can I use a similar approach for resources? I want to expect success : false to be failure but from a resource's point of view that's a 200 HTTP. Take a look at my question as well please : http://stackoverflow.com/questions/17038702/angular-resource-error-handling-how-can-i-treat-a-success-false-that-comes-f/17038902?noredirect=1#17038902 – Vlad Nicula Jun 11 '13 at 07:48
  • 4
    Need to return $q.reject(response); when status == 401, to avoid a noisy angular error – s_t_e_v_e Jul 29 '13 at 01:40
  • good stuff but should use `$location.path('#/')` not the global `window.location` object – SavoryBytes Aug 30 '13 at 17:04
  • 1
    @daniellmb. It depends. If you actually want to go to another page, not just change the view then you should actually use $window. If your login page is just another view and controller with your angular then you can use $location.path – uriDium Oct 03 '13 at 13:18
  • 1
    @uriDium right my point was to use the angular provided objects so you can mock and test. – SavoryBytes Oct 04 '13 at 22:58
  • 22
    $httpProvider.responseInterceptors is now deprecated. See http://docs.angularjs.org/api/ng.$http#description_interceptors. – quartzmo Feb 12 '14 at 17:45
  • 1
    In success you need to return like `return response || $q.when(response);` so that if the response is empty then also a promise object is returned. – Ashish Gaur Feb 20 '15 at 11:20
  • The blog article URL that Justen referred has been changed to: http://www.espeo.pl/1-authentication-in-angularjs-application/ – Evi Song Apr 22 '15 at 01:04
  • this probably won't work with agressive minification but the idea is good – Rivenfall May 31 '16 at 10:28
77

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')
Samuli Pahaoja
  • 2,660
  • 3
  • 24
  • 32
MikeR
  • 910
  • 8
  • 8
  • But you will have no xhr data or other useful info in responseError interceptor. It's even unusable to decide if it's recoverable. – zw0rk Oct 11 '13 at 10:55
  • 1
    @zw0rk You will... inside of `responseError`, `rejection` has everything you need. – Langdon Jan 31 '14 at 16:33
  • Does that last line, `$httpProvider...` get wrapped in a `config()` block? – delwin Feb 12 '14 at 20:02
  • indeed, I've edited my answer to show how I did it in my project using Coffeescript. Use http://js2coffee.org/ if you prefer it in Javascript. – MikeR Feb 15 '14 at 16:23
  • Shouldn't all the reference to `response` under the `responseError` function actually be references to `rejection` (or maybe the parameter should have its name changed to `response`? – Adam Nofsinger Mar 07 '14 at 15:14
  • I created a fiddle with different edge cases (Black Hole serivce, Response Codes etc.): http://jsfiddle.net/dv1vobsn/ – simonox Jul 28 '15 at 11:10
16

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);
}());
Jan-Terje Sørensen
  • 14,468
  • 8
  • 37
  • 37
  • @ThilakRaj the above code should run on every http request. So make two breakpoints in Chrome, one on the ´return response´ line and one on the ´return $q.reject´ to check that it runs as it should. – Jan-Terje Sørensen Feb 01 '16 at 12:35