7

I'd like to be able to set up an AngularJS http interceptor which will set $rootScope.loading to true or false, depending on whether an AJAX request is currently ongoing.

So far I've put the following together:

angular.module('myApp')
.config(function ($httpProvider) {
  $httpProvider.responseInterceptors.push('loadingInterceptor');

  var loadingFunction = function (data, headersGetter) {
      $rootScope.loading = true

      return data;
  };
  $httpProvider.defaults.transformRequest.push(loadingFunction);
})
.factory('loadingInterceptor', function ($q, $window, $rootScope) {
    return function (promise) {
        return promise.then(function (response) {
            $rootScope.loading = false;
            return response;

        }, function (response) {
            $rootScope.loading = false;
            return $q.reject(response);
        });
    };
});

But I'm unable to inject $rootScope into the config block, so I have no way of setting the $rootScope.loading variable when an HTTP request begins.

Am I missing something here? How should I do this?

JP.
  • 5,507
  • 15
  • 59
  • 100

3 Answers3

13

responseInterceptors has been deprecated http://docs.angularjs.org/api/ng.$http. I have enhanced the previous example:

app.factory('myHttpInterceptor', ['$q', '$rootScope', '$injector',
    function ($q, $rootScope, $injector) {
        $rootScope.showSpinner = false;
        $rootScope.http = null;
        return {
            'request': function (config) {
                $rootScope.showSpinner = true;
                return config || $q.when(config);
            },
            'requestError': function (rejection) {
                $rootScope.http = $rootScope.http || $injector.get('$http');
                if ($rootScope.http.pendingRequests.length < 1) {
                    $rootScope.showSpinner = false;
                }
                if (canRecover(rejection)) {
                    return responseOrNewPromise
                }
                return $q.reject(rejection);
            },
            'response': function (response) {
                $rootScope.http = $rootScope.http || $injector.get('$http');
                if ($rootScope.http.pendingRequests.length < 1) {
                    $rootScope.showSpinner = false;
                }
                return response || $q.when(response);
            },
            'responseError': function (rejection) {
                $rootScope.http = $rootScope.http || $injector.get('$http');
                if ($rootScope.http.pendingRequests.length < 1) {
                    $rootScope.showSpinner = false;
                }
                if (canRecover(rejection)) {
                    return responseOrNewPromise
                }
                return $q.reject(rejection);
            }
        }
    }
]);

And you can use the factory like so:

app.config(function ($httpProvider) {
    $httpProvider.interceptors.push('myHttpInterceptor');
});
Langdon
  • 19,875
  • 18
  • 88
  • 107
Tshepo Mgaga
  • 272
  • 3
  • 10
  • canRecover is undefined b/c this should be custom logic for your use case (ie check for 401 unauthorized etc) – timelfelt Jul 09 '15 at 21:45
3

You can only insert providers in module.config, but you shouldn't do this in the default transformers anyway, you should do it in the interceptors (like you do when you're setting loading = false).

There might be more than one request going on at the same time. Something like this might work:

angular.module('myApp')
  .config(function ($httpProvider) {
    $httpProvider.responseInterceptors.push(function ($rootScope) {
      $rootScope.numLoadings = 0;
      $rootScope.loading = false;
      return function (promise) {
        $rootScope.numLoadings++;
        $rootScope.loading = true;
        // make sure the loading screen is visible
        var hide = function (r) {
          if ((--$rootScope.numLoadings)===0){
            //console.log('hide the loading screen');
            $rootScope.loading = false;
          }
          return r;
        };
        return promise.then(hide, hide);
      };
    });
  });

Ofcourse you don't have to put numLoadings in the root scope, I just put it there for this example: http://plnkr.co/edit/32Mh9UOS3Z4vnOtrH9aR?p=preview

joakimbl
  • 18,081
  • 5
  • 54
  • 53
1

module.config deals only with providers and you shouldn't try to access $rootScope from here.

Also, I see no reason to calculate number of pending requests manually since it's provided by the $http service and, well... there's no need to do the same job twice:)

Coffeescript

angular.module('app.services.networkStatusIndicator', []).config([

  '$httpProvider'

  ( $httpProvider )->

    # Network status indicator
    # ========================
    # Monitor XHTTP requests globally and update UI accordigly.

    $http = null
    interceptor = ['$q', '$injector', '$rootScope' , ($q, $injector, $rootScope )->
      success = (response)->
        $http = $http or $injector.get '$http'
        if $http.pendingRequests.length < 1
          $rootScope.networkStatus = 'idle'
        response

      error = (response)->
        $http = $http or $injector.get '$http'
        if $http.pendingRequests.length < 1
          $rootScope.networkStatus = 'idle'
        $q.reject response

      (promise)->
        $rootScope.networkStatus = 'loading'
        promise.then success, error
    ]
    $httpProvider.responseInterceptors.push interceptor
])

Javascript

angular.module('app.services.networkStatusIndicator', []).config([
  '$httpProvider', function($httpProvider) {
    var $http, interceptor;
    $http = null;
    interceptor = [
      '$q', '$injector', '$rootScope', function($q, $injector, $rootScope) {
        var error, success;
        success = function(response) {
          $http = $http || $injector.get('$http');
          if ($http.pendingRequests.length < 1) {
            $rootScope.networkStatus = 'idle';
          }
          return response;
        };
        error = function(response) {
          $http = $http || $injector.get('$http');
          if ($http.pendingRequests.length < 1) {
            $rootScope.networkStatus = 'idle';
          }
          return $q.reject(response);
        };
        return function(promise) {
          $rootScope.networkStatus = 'loading';
          return promise.then(success, error);
        };
      }
    ];
    return $httpProvider.responseInterceptors.push(interceptor);
  }
]);
Rafal Pastuszak
  • 3,100
  • 2
  • 29
  • 31