Count pending $q promises in angularjs


Erie

I am developing a complex angularjs application. Complexity means that a single view may have multiple different data sources that are queried from different places in the code, such as a page section controller or a directive controller. Sometimes some of these requests may take longer to resolve, leaving the view in some invalid state. I'd like to be able to display some sort of "data loading" banner and disable user interaction with the view until all promises are resolved. This works for simple screens using the $q.all() function. However, the way applications are built is more business logic centric than data source centric. Therefore, for more complex screens, there will be no one place to naturally access all the promises made. It seems like a hassle to create such a place in code.

I came up with this solution:

angular.module('myApp').service('qConfigurer', function ($q) {
    var pending = 0;
    var origDefer = $q.defer;

    $q.defer = function() {
        pending++;
        var result = origDefer.apply(arguments);

        var origResolve = result.resolve;
        var origReject = result.reject;

        result.resolve = function() {
            pending--;
            return origResolve.apply(arguments);
        };

        result.reject = function() {
            pending--;
            return origReject.apply(arguments);
        };

        return result;
    };

    $q.pending = function() {
        return pending;
    };

    return {};
});

Are there less tricks to achieve the same?

Benjamin Gruenbaum

What you are doing is modifying the global state and then hacking the changes $q, this kind of AOP can easily cause problems for 3rd party module plugins, which I think is dangerous, it doesn't let you determine the scope of the changes, let alone the speed penalty.

I think better

It seems to me that what you really want is an approach to resource management, one from try(resource)Java, using(C# or withPython . Unfortunately, the only promise implementation with this functionality is Bluebird, we use $q here, so let's make one :)

So we want to have a promise-based function and decrement it by one from the counter regardless of the result - in our case our resource forms a semaphore .

function loading(fn){  // takes a function that returns a promise, put in a service
      var args = Array.prototype.slice.call(arguments,1);
      return $q.when().then(function(){
          loading.counter++; // signal the scope somehow, either by having the counter
                             // on the scope and accepting it as a param, by a watcher or
                             // with an emit
      }).then(function(){
          return fn.apply(null, args); // can add context param if you want for `this`
      }).finally(function(){
          loading.counter--; // signal just like with the above
      });
}
loading.counter = 0;

Then, the usage becomes:

 // you can use it like this
 loading(function(){
     return myService.apiCall(...);
 }).then(function(result){
      $scope.a = result;
 });
 // or like this
 loading(myService.apiCall,...).then(function(result){
     $scope.b = result;
 });

If you put show/hide logic, event hooks, scope variables, or input parameters in the load function (I keep this parameter for personal preference , do something when it's zero and when it's 1 ) - it will Show/hide loading screen.

Related