1

Let's say you have:

function myPromise() {
  // using some promise lib other than $q, like Q or bluebird
  var defer = Q.defer();
  defer.resolve('John');
  return defer.promise;
}

and then you have something like:

function foo() {
  return myPromise().then(function (name) {
    return name + ' Smith';
  });
}

foo().then(function (name) {
  $scope.$apply(function () { // or can use $timeout
    console.log('after foo');
    $scope.text = 'Hey ' + name;
  });
});

Is there a way to modify foo so that the after foo block is automatically wrapped in $scope.$apply()? i.e. we can then just have:

foo().then(function (name) {
  console.log('after foo');
  $scope.text = 'Hey ' + name;
  // $scope.$apply() is automatically called after this block is executed
});

The reason I ask is that I am developing an API where the user will make several of these foo calls and it would greatly cut down on the user's code if the digest cycle was automatically triggered after the foo promise resolves. I know I can use a callback to accomplish this, but I'm hoping there is a cleaner way that just uses promises.

My hunch is that the solution lies in wrapping the non-$q promise in a $q promise...

redgeoff
  • 3,163
  • 1
  • 25
  • 39
  • You dont need `$scope.$apply()` there. See this http://plnkr.co/edit/g1pBmU?p=preview I have deliberately wrapped in setTimeout, which does not trigger any digest cycle itself. Your issue could be elsewhere. digest cycle gets invoked after every promise chain. – PSL Sep 23 '14 at 00:41
  • @PSL I just noticed that I had a mistake... myPromise is a promise from a non-$q promise library. See my changes above. – redgeoff Sep 23 '14 at 01:06
  • one way you could by creating an angular version of this service wrapping this service with angular promise. – PSL Sep 23 '14 at 01:27

2 Answers2

1

There is no way to do this on Angular's end at the moment. From the other direction the Q promise library does not expose a hook for this either.

So, you have two options:

  • Override the then method of all Q non-angular promises, this will work because of how Q is structured but is sort of risky and can break in future versions of Q ( so be careful in upgrades).
  • Use a promise library like Bluebird which lets you use Angular seamlessly like you want without any issues with a short snippet. With Bluebird this question is a lot simpler.

I strongly suggest to do the latter and using a promise library that lets you set the scheduler. It has better support and it doesn't create an excess promise. However, if you want to use the first here's how:

var oldThen = Q.Promise.prototype.then; // reference `then` 
Q.Promise.prototype.then = function(){
    return oldThen.apply(this, arguments). // decorate
           then(function(value){
                $rootScope.$evalAsync(function(){}); // schedule digest
                return value; // keep value
           },function(err){
               $rootScope.$evalAsync(function(){}); // schedule digest
               return $q.reject(err); // keep rejection
           });
};

Basically we're making each Q deferred here schedule a digest if one is not scheduled after it is done. On why this works you can read this question and answer.

(credit notice - I took the above code from a discussion I was part of. Most of the above code was written by me but some by Angular's Jeff Cross, so credit for him too)

Community
  • 1
  • 1
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • "Decorate" is a cool term. Is there any precedent for using the word in this way? – Roamer-1888 Sep 23 '14 at 11:17
  • 1
    @Roamer-1888 sure, it's actually a widely used term and design pattern that spans across languages - http://en.wikipedia.org/wiki/Decorator_pattern . One example for a language with built in support is [Python](http://legacy.python.org/dev/peps/pep-0318/). In JavaScript we don't need to do the subclass + compose trick because interfaces are implicit and functions are first order. – Benjamin Gruenbaum Sep 23 '14 at 11:56
  • Thanks Benjamin, interesting stuff. – Roamer-1888 Sep 23 '14 at 20:04
0

I was able to trigger the digest cycle just by wrapping my API functions in $timeout blocks, e.g.

function foo() {
  return $timeout(function () {
    return myPromise().then(function (name) {
      return name + ' Smith';
    });
  });
}
redgeoff
  • 3,163
  • 1
  • 25
  • 39