9

Consider:

angular
  .module('app', [])
  .controller('MainController', function($scope) {
    $scope.$watch('bool', function(newVal, oldVal) {
    });
    console.log($scope);
  });

and

<body ng-controller='MainController'>
  <p ng-class="{'blue': bool, 'red': !bool}">ngClass</p>
  <p ng-show='bool'>ngShow</p>
  <input type='checkbox' ng-model='bool' />
</body>

plnkr of above

It seems that there are 3 watchers being created:

  1. From $scope.$watch.
  2. From ngShow.
  3. From ngClass.

(Note: directives involved in data binding use $scope.$watch internally.)

enter image description here

I would have thought that since they're all watching the bool property, there'd only be one watcher and it'd have multiple listener callbacks.


Edit: is it the case that it's saying, "Has bool changed? If so run cb1. Has bool changed? If so run cb2. Has bool changed? If so run cb3." Or is it the case that it's saying, "Has bool changed? If so run cb1, cb2, and cb3." If the former, why do that over the latter?

Questions:

  1. Is my interpretation correct? Are there actually multiple watches being registered?
  2. What are the implications for performance?
  3. Bonus: if my interpretation is correct and multiple watchers are being added, why would it be designed like this? Why look for changes to bool 3 times instead of 1?

Example for 2) - say you want to make sure that two passwords in a form match, and if they don't, show an error. Assume that you already have:

ng-class="{invalid: myForm.myInput1.$touched && ctrl.myInput1  != ctrl.myInput2}" 

Say you want to use $setValidity to update the validity of the form. Might it be a good idea to do:

ng-class="{invalid: myForm.myInput1.$touched && ctrl.functionToCheckInputs(myForm)}"

and call $setValidity inside of functionToCheckInputs rather than using $scope.$watch and doing $setValidity inside of it? Because the latter adds an additional watcher (presumably).

Community
  • 1
  • 1
Adam Zerner
  • 17,797
  • 15
  • 90
  • 156
  • There may be many watchers in application. Please follow this answer. http://stackoverflow.com/questions/18634666/how-does-angularjss-watch-function-work and http://stackoverflow.com/questions/18499909/how-to-count-total-number-of-watches-on-a-page and https://www.accelebrate.com/blog/effective-strategies-avoiding-watches-angularjs/ – Vish Jul 22 '15 at 17:26
  • Seems you are only considering the `bool` but can clearly see that the `fn: function` is different for each of the 3 – charlietfl Jul 22 '15 at 17:26
  • @charlietfl See my recent edit. – Adam Zerner Jul 22 '15 at 17:30
  • `Might it be a good idea to do....` ... avoid putting functions in the markup other than for things like event handlers. Many digests can run per scope change...if returned value of function also changes can force even more digests – charlietfl Jul 22 '15 at 17:41
  • 2
    Angular is watching expressions. Neither can Angular know in advance that two expression refer to the same property, nor is there any guarantee that it will stay that way. – a better oliver Jul 25 '15 at 22:24
  • @zeroflagL can you elaborate? If there are two strings/variables on the same scope, wouldn't Angular know that they'd evaluate to be the same thing? – Adam Zerner Jul 31 '15 at 14:10
  • How is Angular supposed to know that the two expressions "are on the same scope"? Even if they are at the beginning, a new scope could be introduced any time and suddenly the expressions evaluate to different values. So `bool` `$scope.$watch('bool' and in `ng-class="{'blue': bool, 'red': !bool}"` could be different. According to the Angular team up to 2000 watchers shouldn't be a problem. – a better oliver Jul 31 '15 at 15:31
  • `How is Angular supposed to know that the two expressions "are on the same scope"?`. I was thinking that when you do `$scope.$watch('bool'...`, it'd look at `$scope.$$watchers` to see if the expression exists or not. It'd be looking at that particular scope's `$$watchers` property. – Adam Zerner Jul 31 '15 at 16:34
  • Fair enough. What do you think: How many watchers in a typical application are affected? It's just not worth the additional complexity. If there actually were too many watchers watching the same expression (or too many in general), then I would rethink my code. Btw: Only two watchers are created for `bool`. – a better oliver Jul 31 '15 at 19:32

5 Answers5

3

There are multiple $watchers being registered. In fact, even if you had 2 exact expressions:

$scope.$watch("foo", cb1)
$scope.$watch("foo", cb2)

you'd still get 2 $watchers.

To answer your question - it is the former case, i.e. "if "foo" expression changed, run cb1, if "foo" expression changed, run cb2, etc... Why? Because, any $watcher could potentially change the return value of $scope.foo; not just in the callback, but in the expression itself. Angular needs to re-evaluate the expressions every time to account for that possibility.

The length of the digest cycle plays a significant factor on performance.

First, is the number of $watchers, which cause a watched expression or function to evaluate. So, reducing the number of $watchers, like preferring one-way to two-way watch, or use one-time watch where suitable, improves performance.

Second, is the complexity of the watched functions. These functions should be very fast - ideally, not more than getters. For example, avoid the following:

<div ng-class="{active: isActive(id)}">
$scope.isActive = function(id){
   for (var i=0; i<items.length; i++){
      if (items[i].id == id && items[0].active) return true;
   }
   return false;
};
New Dev
  • 48,427
  • 12
  • 87
  • 129
2

Is my interpretation correct? Are there actually multiple watches being registered?

So to answer this I refer back to a another very good stack article. How to count total number of watches on a page?

This was great pre angular AngularJS 1.3.2 when a countWatchers method was added to the ngMock module.

Now you can count the number of watches on the item, which you are correct, there are 3 two way relationships that have been created that will watch.

What are the implications for performance?

This is a little harder to quantify, the more watchers the less performance it will have. My suggestion would be to use Batarang to judge the performance of your watchers to insure you are not bloating your app. https://chrome.google.com/webstore/detail/angularjs-batarang-stable/niopocochgahfkiccpjmmpchncjoapek

Also approx 2000 watches in my own personal apps is when I have noticed significant performance issues.

Bonus: if my interpretation is correct and multiple watchers are being added, why would it be designed like this? Why look for changes to bool 3 times instead of 1?

Watching is made up of a two way relationships. This has been one of the struggles with angular because there is a fast way of doing something in angular and a more optimal way. If you had made that markup be a directive with bool used in a template then there would have been only 1 watch linked to the directive. Angular 2.0 should improve upon this.

Community
  • 1
  • 1
James
  • 1,514
  • 12
  • 30
1

Many variable same time use in single watch..if any one change made then call watch function

    $scope.$watch('bool+bool2+bool3+bool4', function(newVal, oldVal) {
   console.log("value changed");   
 });
  • 1
    Cool to know, but that doesn't address the question I was asking. – Adam Zerner Jul 31 '15 at 13:22
  • I made this [plnkr](http://plnkr.co/edit/OQNbME2iziyTnvjXGOv0?p=preview) to demo. I can't tell whether or not it creates additional watchers. It doesn't seem to. Regardless of the expression, I'm getting `$scope.$$watchers` to be an array of 7 elements, with only one of the elements referring to the bools (see plnkr for details). – Adam Zerner Jul 31 '15 at 13:29
0

Yes.

   $scope.$watch('bool', function(newVal, oldVal) {    
   console.log("bool changed");
   });

   $scope.$watch('bool2', function(newVal, oldVal) {
       console.log("bool2 changed");
   });
0

In your created plunkr, if you go deep down the scope that logged, you will see that those watchers are for the bool1,bool2,bool3. Angular does create multiple watchers. i don't have 10 reputation points to upload an image here. But see http://postimg.org/image/833b1q865/ for reference