40

I've found an example of a loading spinner for http/resource calls here on SO:

As you can see the implementation works (using AngularJS 1.0.5). However if you change the sources to AngularJS 1.1.5. The example does not work anymore.

I learned that $httpProvider.responseInterceptors is deprecated in 1.1.5. Instead one should use $httpProvider.interceptors

Unfortunately just replacing the above string in the Plunker did not solve the problem. Has anyone ever done such a loading spinner using HttpInterceptor in AngularJS 1.1.5?

Thanks for your help!

Michael

Community
  • 1
  • 1
Michael Hunziker
  • 2,659
  • 3
  • 22
  • 26
  • The plunk works for me. What doesn't work for you? What result are you expecting? – Ye Liu Jul 24 '13 at 17:02
  • 1
    Instead on manually intercepting/counting request, you can just watch for `$http.pendingRequests.length > 0` – Jscti Jul 24 '14 at 15:32

3 Answers3

112

Thanks to Steve's hint I was able to implement the loader:

Interceptor:

.factory('httpInterceptor', function ($q, $rootScope, $log) {

    var numLoadings = 0;

    return {
        request: function (config) {

            numLoadings++;

            // Show loader
            $rootScope.$broadcast("loader_show");
            return config || $q.when(config)

        },
        response: function (response) {

            if ((--numLoadings) === 0) {
                // Hide loader
                $rootScope.$broadcast("loader_hide");
            }

            return response || $q.when(response);

        },
        responseError: function (response) {

            if (!(--numLoadings)) {
                // Hide loader
                $rootScope.$broadcast("loader_hide");
            }

            return $q.reject(response);
        }
    };
})
.config(function ($httpProvider) {
    $httpProvider.interceptors.push('httpInterceptor');
});

Directive:

.directive("loader", function ($rootScope) {
    return function ($scope, element, attrs) {
        $scope.$on("loader_show", function () {
            return element.show();
        });
        return $scope.$on("loader_hide", function () {
            return element.hide();
        });
    };
}
)

CSS:

#loaderDiv {
   position: fixed;
   top: 0;
   right: 0;
   bottom: 0;
   left: 0;
   z-index: 1100;
   background-color: white;
   opacity: .6;
}

.ajax-loader {
   position: absolute;
   left: 50%;
   top: 50%;
   margin-left: -32px; /* -1 * image width / 2 */
   margin-top: -32px; /* -1 * image height / 2 */
   display: block;
}

HTML:

<div id="loaderDiv" loader>
    <img src="src/assets/img/ajax_loader.gif" class="ajax-loader"/>
</div>
Michael Hunziker
  • 2,659
  • 3
  • 22
  • 26
  • 3
    F**** A man ! First time I copy paste a script and it works straight away with no modification ! – Bgi Jan 01 '14 at 21:07
  • Nice work! Save me a trouble implementing this. As Bgi said, worked straight copy/paste the script :D – CodeOverRide Jan 06 '14 at 20:44
  • What a slick, easy-to-understand answer. Rock on! – leebrandt Mar 06 '14 at 16:25
  • That was surprisingly easy to implement. – Ryan Arneson Jul 30 '14 at 15:43
  • 1
    @Bixi mentioned, replace counters with code:- var _http = null; (top of return), inside response and responseError, _http = _http || $injector.get('$http') and check for _http.pendingRequests.length < 1 – parsh Aug 15 '14 at 01:53
  • Done a great job, you have. – Rutwick Gangurde Oct 14 '14 at 06:50
  • what if we have 2 http requests which start the loading at same time but end it at different time like http://codepen.io/anon/pen/emZWON?editors=101 – divyenduz Dec 12 '14 at 11:22
  • used something similar... loved it... +1 – Jony-Y Mar 30 '15 at 08:26
  • Just need some clarification - why `if (!(--numLoadings)) {` inside the `responseError` function and not `if ((--numLoadings) === 0) {` as in the `response` function? Sorry, not really sure what `!(--numLoadings))` mean? (its treating a number as if its a boolean?) – dutoitns Sep 30 '15 at 14:10
  • This solution can not distinguish between areas. Example loading different panels from server would show all loading until last request has got its response. BUT I will use this idea to disable all buttons during post/put/delete request – Jens Alenius Oct 15 '15 at 13:45
  • 1
    This is great...EXCEPT...it is jQuery dependent: element.show/.hide are not supported in angular's JQLite. – Cos Callis Nov 11 '15 at 21:32
  • what about infinite loader. how do we disable this external loader and show a different loader for infinite data loading. – ashishkumar148 Dec 18 '15 at 07:03
  • If you don't like broadcasting, you can replace the directive and boradcasts by a factory that provides getSpinnerState() and setSpinnerState() methods. – TrtG Feb 26 '16 at 09:54
  • `element[0].style.display='block'` for `element.show()` `element[0].style.display='none'` for `element.hide()` if you don't want to use Jquery. – Bipin Bhandari May 21 '16 at 07:52
  • It will be better to use emit instead of broadcast. – Anirudha Jul 28 '16 at 12:43
  • Caution: `return config || $q.when(config)` and `return $q.reject(response);` is necessary, if you remove and have `.catch()` handlers of `$http`request, will stop working correctly – elporfirio Mar 21 '17 at 16:26
18

"responseInterceptors" was deprecated. "interceptors" replaced it with many enhancements in a preview version. Off the top of my head I don't remember which version. Documentation on this is sparse, so you're probably best off examining the source code.

The gist of the change looks like this:

$httpProvider.interceptors.push(function($q, $rootScope) {
  return {
     'request': function(config) {
        // intercepts the request
     },
     'response': function(response) {
       // intercepts the response. you can examine things like status codes
     },
     'responseError': function(response) {
       // intercepts the response when the response was an error
     }
  }
});

In the angular source you will find documentation under "* # Interceptors" in the $HttpProvider function. There is an example usage very similar to what I posted above.

Steve Davis
  • 716
  • 5
  • 13
2

The provided/accepted solution is fine IF you want to include JQuery in your solution, which the AngularJS team is recommending against going forward. element.show/.hide are not supported in Angular's JQLite.... So the following refactors are necessary to run in an non-jquery session:

Change the HTML element to add a class of 'hidden'

<div id="loaderDiv" loader class="hidden">
     <img src="Content/images/yourgif.gif" class="ajax-loader" />
</div>

Add the hidden class to your css:

.hidden{display:none !important}

And tweak the directive thus:

(function() {
    'use strict';

    angular
        .module('your_app')
        .directive('yourSpinner', yourSpinner);

    yourSpinner.$inject = ['$rootScope'];

    function yourSpinner($rootScope) {
       return function($scope, element, attrs) {
           $scope.$on("loader_show", function () {
               if (element.hasClass("hidden")) {
                   element.removeClass("hidden")
               }
            });
            return $scope.$on("loader_hide", function () {
                if(!element.hasClass("hidden")){
                    element.addClass("hidden")
                }
            });
        }
    }

})();

The factory is fine as-is.

Cos Callis
  • 5,051
  • 3
  • 30
  • 57