Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

$scope.$watch doesn't trigger on every change

I'm using angularJS 1.4.8, yesterday i noticed that the $scope.$watch doesn't trigger on every change which caused bug in my application.

is there a way to force it to work on every change immediately ? like in this code, in every change on message i want the function in watch to trigger:

(function(){

  angular.module('myApp', [])
  .controller('myAppController', myAppController)

  function myAppController($scope){
    console.log('controller loading !');
    $scope.message = 'message1';

    $scope.$watch('message', function(newMessage){
      console.log('newMessage', newMessage)
    });

    function changeMessage(){
      $scope.message='hi';
      $scope.message='hi12';
    }

    changeMessage(); 
  }

})();

the console will print:

controller loading !
newMessage hi22

plunker link https://plnkr.co/edit/SA1AcIVwr04uIUQFixAO?p=preview

edit: I would really like to know if there are any other ways than wrapping the change with timeout and using scope apply, in my original code iv'e multiple places where i change the scope property and i would like to avoid using this every change.

like image 781
Sahar Sabin Avatar asked Sep 05 '17 06:09

Sahar Sabin


4 Answers

This happens because the watch will only be triggered if the value is changed "between" digest loops.

Your function is changing the message value on the scope in the same function. This will be executed in the same digest loop. When angular moves on to the next loop it will only see the last changed value which in your case will be hi22.

Here's a great article which makes this behaviour clear

like image 113
Marcus Höglund Avatar answered Nov 12 '22 11:11

Marcus Höglund


update your changeMessage function so that it uses $scope.$apply function which will ensure that your changes are reflected and angular is aware of your changes to the variable.

changeMessage() {
   setTimeout(function () {
        $scope.$apply(function () {
          $scope.message = "Timeout called!";
        });
    }, 2000);
}
like image 41
Ajinkya Dhote Avatar answered Nov 12 '22 11:11

Ajinkya Dhote


If you change value into the same digest cycle the watcher is not triggered and last value is taken. When we run $timeout, we change $scope.message value in next digest cycle and watcher catches it as expected.

Take look on simple test:

 $scope.$watch(function(){
  console.log('trigger');
  return $scope.message;
},
  function(newMessage){
  console.log('newMessage', newMessage)
});

function changeMessage(){
  $scope.message='hi';

  $timeout(function(){
    $scope.message='hi12';
  });      
}

Output:

controller loading !
 trigger
 newMessage hi
 trigger
 trigger
 newMessage hi12
 trigger
like image 24
Maxim Shoustin Avatar answered Nov 12 '22 09:11

Maxim Shoustin


There is no need to wrap changeMessage in setTimeout and $apply at the same time. If you need to skip some time before execution, just use:

function changeMessage(){
    $timeout(function(){
        $scope.message = 'message';
    }/* or add time here, doesn't matter */);
}

Or just:

function changeMessage(){
    $scope.message = 'message';
    $scope.$apply();
}

Both methods calls $rootScope.$digest in the end. Here is more information: https://www.codingeek.com/angularjs/angular-js-apply-timeout-digest-evalasync/

like image 3
Kindzoku Avatar answered Nov 12 '22 11:11

Kindzoku