Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular JS: Chaining promises and the digest cycle

NOTE: the fiddle uses an old version of Angular, and that it's not working any more because as of 1.2 the Angular template engine does not handle promises transparently.

I'm looking into chaining promises to populate my scope, and then having the scope automatically update the dom.

I'm running into problems with this though.. If I call "then" on an already resolved promise, it creates a new promise (that will call the success function asynchronously but almost immediately). I think the problem is that we've already left the digest cycle by the time the success function is called, so the dom never updates.

Here is the code:

<div ng-controller="MyCtrl">
    Hello, {{name}}! <br/>
    {{name2}}<br/>
    <button ng-click="go()">Clickme</button><br/>
    {{name3}}
</div>

var myApp = angular.module('myApp',[]);

function MyCtrl($scope, $q) {
    var data = $q.defer();    
    setTimeout(function() {$scope.$apply(data.resolve("Some Data"))}, 2000);
    var p = data.promise;

    $scope.name = p.then(angular.uppercase);
    $scope.name2 = p.then(function(x) { return "Hi "+x;});
    $scope.go = function() {
            $scope.name3 = p.then(function(x) { 
                // uncomment this to make it work:
                //$scope.$apply();
                return "Finally: "+x;
            });
    };
 }

http://jsfiddle.net/QZM4d/

Is there some way to make this work without calling $apply every time I chain promises?

like image 547
Karen Zilles Avatar asked Feb 02 '13 01:02

Karen Zilles


1 Answers

NOTE: the fiddle uses an old version of Angular, and that it's not working any more because as of 1.2 the Angular template engine does not handle promises transparently.

To quote @pkozlowski.opensource:

In AngularJS the results of promise resolution are propagated asynchronously, inside a $digest cycle. So, callbacks registered with then() will only be called upon entering a $digest cycle.

So, when the button is clicked, we are in a digest cycle. then() creates a new promise, but the results of that then() will not be propagated until the next digest cycle, which never comes (because there is no $timeout, or $http, or DOM event to trigger one). If you add another button with ng-click that does nothing, then click that, it will cause a digest cycle and you'll see the results:

<button ng-click="">Force digest by clicking me</button><br/>

Here's a fiddle that does that.

The fiddle also uses $timeout instead of setTimeout -- then $apply() isn't needed.

Hopefully it is clear when you need to use $apply. Sometimes you do need to call it manually.

like image 133
Mark Rajcok Avatar answered Oct 07 '22 15:10

Mark Rajcok