1

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)
  });
james246
  • 1,884
  • 1
  • 15
  • 28
  • The question is how is foo updated? If it is only ever updated in the controller, then there is no need for a `watch`! Regular javascript will do here. If it is updated through a form input (for example) then yes you can use a `watch`. What you can also do is to refactor the `watch` function into a separate function call that can be run initially to set everything up. See my example in the answer below. – Umair Jul 23 '15 at 11:55

3 Answers3

2

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';
    }
});
Umair
  • 3,063
  • 1
  • 29
  • 50
1

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.

deonclem
  • 860
  • 10
  • 25
  • Thanks for your answer, that makes sense. I've added another example to the OP with a scenario which mirrors my real use-case: An update of variable (`foo`) triggers an update of another variable (`bar`) within the `$watch` on `foo`, but the updated value of `bar` isn't available immediately. If I wrap the `console.log($scope.bar);` in a `$timeout` then it all works, but is there a better way to approach this problem perhaps? – james246 Jul 23 '15 at 10:59
  • 1
    Why don't you just call the function you want executed when you update $scope.foo **in your controller** ? Using `$watch` only really makes sense when you're expecting your variable to be modified from a view update, not when you're manually changing from inside your controller. I'm still not really sure of what you're trying to achieve here. – deonclem Jul 23 '15 at 14:26
  • The example is simplistic, but perhaps an important detail that I omitted is that the first variable (`foo`) can be updated from either the view or the controller. So the suggestion from both you and @Umair of having a plain function for the controller, and leaving $watch to just detect changes from the view makes sense. My key misunderstanding of $watch was that it would be a good place to put code that should be run if a variable changes either as a result of an action in the view or some other update in the controller. – james246 Jul 23 '15 at 18:06
0

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?

Community
  • 1
  • 1
Thom-x
  • 841
  • 1
  • 6
  • 20
  • It's not too quick! `$watch` is fired only during `$digest` cycle and it's not running every time you change value (unless it's managed by ngModel) What you did here is to provoke digest 3 times – maurycy Jul 23 '15 at 10:46