Two way bind doesn't work with a setTimeout


#1

I have this button:

<button class="button button-block icon ion-play button-assertive mid" ng-show="!sound_on" ng-click="soundPlayer()"></button>
<button class="button button-block icon ion-pause button-assertive mid" ng-show="sound_on" ng-click="soundPlayer()"></button>

This only shows one button at a time dependant on the $scope.sound_on.

And this is the code that gets it working.

$scope.soundPlayer = function(){
    if($scope.sound_on==false){
      $scope.sound_on=true;
      media.setVolume('1.0');
      media.play();      
      setTimeout(function(){ 
         $scope.sound_on=false;
         console.log("should change");
      }, 12600);
    }else{
      media.stop();
      $scope.sound_on=false;
    }
  }

The $scope.sound_on=false in the setTimeout just doesn’t work. Clicking the button bound to soundPlayer() works fine to toggle the icon on the button with ng-if or ng-show) but I want it to flip back off when the sound stops playing (after 12.6 seconds).

No idea why it doesn’t work.

Anyone?


#2

Problem is setTimeout() runs outside of Angular’s digest cycle. Couple of ways to solve:

Using Angular $timeout (preferred way - remember to inject $timeout service in your module):

$scope.soundPlayer = function(){
    if($scope.sound_on==false){
      $scope.sound_on=true;
      media.setVolume('1.0');
      media.play();      
      $timeout(function(){ 
         $scope.sound_on=false;
         console.log("should change");
      }, 12600);
    }else{
      media.stop();
      $scope.sound_on=false;
    }
  }

Some advice might be to just call $scope.$apply(); but I don’t recommend that approach as it can cause the ‘digest already in progress error’. The $timeout method (even with timeout of 0) is always safe w.r.t. digest cycle.

Alternative:

$scope.soundPlayer = function(){
    if($scope.sound_on==false){
      $scope.sound_on=true;
      media.setVolume('1.0');
      media.play();      
      setTimeout(function(){ 
         $scope.$apply(function() {
             $scope.sound_on=false;
         });
         console.log("should change");
      }, 12600);
    }else{
      media.stop();
      $scope.sound_on=false;
    }
  }

#3

Yeah, $timeout did it.

I still think in JavaScript! Got to think in Angular!

Thanks.


#4

Great!

I didn’t understand it until I read this amazing post: http://jimhoskins.com/2012/12/17/angularjs-and-apply.html