11

I followed this post to implement a similar ajax loader image on a project:

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.

Community
  • 1
  • 1
rfgamaral
  • 16,546
  • 57
  • 163
  • 275

2 Answers2

3

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.

  • I've tried this on a simple app where I know my interceptor is the only one (I also check the array length). Even though I remove my interceptor it's still being called. Btw, you can also use .slice() if you store the index of where you pushed your interceptor so you don't have to worry about pop'ing other interceptors. – John-Philip Oct 21 '14 at 07:01
  • 2
    @John-Philip Yeah, it seems that once Angular has bootstrapped, altering configuration is a no-no. Popping the interceptor won't work because Angular's already provided the service (using `$get`) and so the configuration has been seeded. –  Feb 09 '15 at 05:01
  • Just a thought. Is it possible to create the interceptor as an injectable object/function (factory or service) and then implement some kind of on/off mechanism for it? – Lars Juel Jensen May 07 '15 at 12:02
  • 3
    @LarsJuelJensen It might be possible. You'd have to hold a reference to the object somewhere else for that to work. Using a closure, you could configure a flag inside the function you're passing for `$get` and do a noop if it's set to true. –  May 07 '15 at 14:02
1

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 :(");
  });
};
Community
  • 1
  • 1
Davide Pastore
  • 8,678
  • 10
  • 39
  • 53
  • 3
    Although your answer describes a way to let the application function as described by OP, the interceptor is still active and will be invoked on each http request. The question was how to remove it from $httpProvider. – Jan Peter Aug 12 '15 at 08:53