james246
james246

Reputation: 1904

$watch not firing as many times as expected

I am expecting $watch to fire x times if you change the variable it is watching x times. In a little example I put together, I am changing the value of variable $scope.foo 3 times, but the associated $watch only runs once...

<html>
  <head>
    <title></title>
    <script src="https://code.angularjs.org/1.3.8/angular.js"></script>
  </head>
  <body ng-app='myApp'>

    <div ng-controller='MyCtrl'></div>

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

      myApp.controller('MyCtrl', function($scope) {
          $scope.$watch('foo', function(oldVal, newVal) {
              console.log(oldVal, newVal);
          });

          $scope.foo = "foo";
          $scope.foo = "bar";
          $scope.foo = "baz";
      });


    </script>
  </body>
</html>

Would anyone be able to explain what the reason for this is, or a different approach I can take to receive the desired outcome?

I am expecting the following console output:

undefined foo
foo bar
bar baz

but get...

baz baz

edit: another example

  myApp.controller('MyCtrl', function($scope, $timeout) {
      $scope.$watch('foo', function() {
          $scope.bar = 'bar';
      });

      $scope.foo = "foo";

      // now the value of $scope.foo has changed, I was expecting the $watch to have run at this point.
      // Apparently is has not because $scope.bar is still undefined. Accessing $scope.bar in a $timeout works, but is there a better way?
      console.log($scope.bar)
  });

Upvotes: 1

Views: 463

Answers (3)

Umair
Umair

Reputation: 3243

A $watch will only fire once during each angular $digest cycle! (If watching a property that is - the simplest scenario).

The three changes you are making to foo are all occurring during the same cycle. And angular will compare the values before the cycle and after the cycle.

For your situation, you need to trigger a new cycle by, for example, changing the values inside a $timeout.

Edit

For your example you could do something like this

myApp.controller('MyCtrl', function($scope) {
    $scope.foo = "foo";
    // run on controller init
    fooWatch();
    // watch for changes
    $scope.$watch('foo', fooWatch);

    function fooWatch() {
        $scope.bar = 'bar';
    }
});

Upvotes: 2

deonclem
deonclem

Reputation: 880

Because a $digest cycle isn't run everytime you change a variable in your scope (fortunately).

It is triggered when you use $timeout though.

What's your use case ?

BTW using $watch in a controller is often not a good practice and easily avoidable.

Upvotes: 1

Thom-x
Thom-x

Reputation: 866

The change is too quick for angular, with $timeout it works :

<html>
  <head>
    <title></title>
    <script src="https://code.angularjs.org/1.3.8/angular.js"></script>
  </head>
  <body ng-app='myApp'>

    <div ng-controller='MyCtrl'></div>

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

      myApp.controller('MyCtrl', function($scope, $timeout) {
          $scope.$watch('foo', function(oldVal, newVal) {
              console.log(oldVal, newVal);
          });

          $scope.foo = "foo";
          $scope.foo = "bar";
          $scope.foo = "baz";
        
          $timeout(function(){
            $scope.foo = "foo";
          },0)        
          $timeout(function(){
            $scope.foo = "bar";
          },0)        
          $timeout(function(){
            $scope.foo = "baz";
          },0)
      });


    </script>
  </body>
</html>

See How does AngularJS's $watch function work?

Upvotes: 0

Related Questions