How to make a loading indicator for every asynchronous action (using $q) in an angularjs-app How to make a loading indicator for every asynchronous action (using $q) in an angularjs-app angularjs angularjs

How to make a loading indicator for every asynchronous action (using $q) in an angularjs-app


Although I find it very complicated, unnecessary and probably broken, you could decorate $q and override its defer function.

Every time someone asks for a new defer() it runs your own version which also increments a counter. Before handing out the defer object, you register a finally callback (Angular 1.2.0 only but always may fit, too) to decrement the counter.

Finally, you add a watch to $rootScope to monitor when this counter is greater than 0 (faster than having pendingPromisses in $rootScope and bind like ng-show="pendingPromisses > 0").

app.config(function($provide) {    $provide.decorator('$q', ['$delegate', '$rootScope', function($delegate, $rootScope) {      var pendingPromisses = 0;      $rootScope.$watch(        function() { return pendingPromisses > 0; },         function(loading) { $rootScope.loading = loading; }      );      var $q = $delegate;      var origDefer = $q.defer;      $q.defer = function() {        var defer = origDefer();        pendingPromisses++;        defer.promise.finally(function() {          pendingPromisses--;        });        return defer;      };      return $q;    }]);});

Then, view bound to a scope that inherits from $rootScope can have:

<span ng-show="loading">Loading, please wait</span>

(this won't work in directives with isolate scopes)

See it live here.


There is a good example in the official documentation working for the current stable 1.2.0.

http://docs.angularjs.org/api/ng.$http (top quarter of the page, search for Interceptors)

My extraction of these documentation lead me to this solution:

angular.module('RequestInterceptor', [])  .config(function ($httpProvider) {    $httpProvider.interceptors.push('requestInterceptor');  })  .factory('requestInterceptor', function ($q, $rootScope) {    $rootScope.pendingRequests = 0;    return {           'request': function (config) {                $rootScope.pendingRequests++;                return config || $q.when(config);            },            'requestError': function(rejection) {                $rootScope.pendingRequests--;                return $q.reject(rejection);            },            'response': function(response) {                $rootScope.pendingRequests--;                return response || $q.when(response);            },            'responseError': function(rejection) {                $rootScope.pendingRequests--;                return $q.reject(rejection);            }        }    });

You might then use pendingRequests>0 in an ng-show expression.


Since requested by the OP, this is based on the method we are using for the app we are currently working on. This method does NOT change the behaviour of $q, rather adds a very simple API to handle promises that need some kind of visual indication. Although this needs modification in every place it is used, it is only a one-liner.

Usage

There is a service, say ajaxIndicator, that knows how to update a portion of the UI. Whenever a promise-like object needs to provide indication until the promise is resolved we use:

// $http example:var promise = $http.get(...);ajaxIndicator.indicate(promise); // <--- this line needs to be added

If you do not want to keep a reference to the promise:

// $http example without keeping the reference:ajaxIndicator.indicate($http.get(...));

Or with a resource:

var rc = $resource(...);...$scope.obj = rc.get(...);ajaxIndicator.indicate($scope.obj);

(NOTE: For Angular 1.2 this would need tweeking, as there is no $then() on the resource object.)

Now in the root template, you will have to bind the indicator to $rootScope.ajaxActive, e.g.:

<div class="ajax-indicator" ng-show="ajaxActive"></div>

Implementation

(Modified from our source.) WARNING: This implementation does not take into account nested calls! (Our requirements called for UI blocking, so we do not expect nested calls; if interested I could try to enhance this code.)

app.service("ajaxIndicator", ["$rootScope"], function($rootScope) {    "use strict";    $rootScope.ajaxActive = false;    function indicate(promise) {        if( !$rootScope.ajaxActive ) {            $rootScope.ajaxActive = true;            $rootScope.$broadcast("ajax.active"); // OPTIONAL            if( typeof(promise) === "object" && promise !== null ) {                if( typeof(promise.always) === "function" ) promise.always(finished);                else if( typeof(promise.then) === "function" ) promise.then(finished,finished);                else if( typeof(promise.$then) === "function" ) promise.$then(finished,finished);            }        }    }    function finished() {        $rootScope.ajaxActive = false;    }    return {        indicate: indicate,        finished: finished    };});