Undefined Variable
Undefined Variable

Reputation: 4267

angular two-way binding not working when called with a timeout

So I have an HTML template inside which I have following angular expression {{player.score}}. The initial score of the player that is stored in a scope object is correctly rendered in the place of the expression.

Now, I have a button click on which this score needs to be updated. If I simple update the players score with a hardcoded value it works correctly:

$scope.updateScore = function (){
     $scope.player.score = 1000; //this is updated without any issues
};

But my problem is that my player score is a complicated calculation, which needs me to use _.defer. So when I wrap my earlier code (for testing) inside _.defer it does not work:

 $scope.updateScore = function (){
         _.defer(function() {
              $scope.player.score = 5000; //this is not updated...
         });
    };

The way I understand _.defer it is just underscores wrapper for setTimeout. I would expect that after whatever delay _.defer uses, when it finally gets around to updating the score, then it will get reflected in the HTML due to the Angular two-way binding.

But this is not happening only when _.defer is used, else it works as expected. Also _.defer is updating the Angular object because if I do a console.log(player.score) in the deferred code, then after a few seconds in the console I do see the update score (5000).

Can any angular/Javascript experts help me understand what I am doing incorrect and how I can fix it. Please note that removing _.defer is not really an option because of various technical/legacy reasons.

I am just trying to figure out why when the object is updated in a deferred manner, the Angular does not update the view.

Any pointers are greatly appreciated.

Upvotes: 3

Views: 527

Answers (2)

Rhumborl
Rhumborl

Reputation: 16609

_.defer takes the code out of the scope of the angular digest, because it internally calls setTimeout.

You either need to manually kick off a digest inside the deferred function so angular rebinds at that point, using $scope.$apply:

$scope.updateScore = function (){
     _.defer(function() {
          player.score = 5000;
          $scope.$apply();
     });
};

or you need to keep the callback inside the angular scope, probably by using $timeout (but remember to inject $timeout into your controller/directive):

$scope.updateScore = function (){
     $timeout(function() {
          player.score = 5000;
     }, 1); // or 0, but _.defer passes 1
};

I would go with option 2 from preference, and I personally can't see how using _.defer can be required over this, but of course it is up to you.

Upvotes: 1

Alon Eitan
Alon Eitan

Reputation: 12025

Angular doesn't "know" about this defer so even if the value gets updated, it won't appear in the view until the next digest loop. You can inject $timeout to your controller and use it like this:

_.defer(function() {
     $timeout(function() {
          $scope.player.score = 5000; //this is not updated...
     });
});

You should also read about deferred objects in angular (https://docs.angularjs.org/api/ng/service/$q)

Upvotes: 2

Related Questions