Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Angular create a new watcher if one already exists?

Tags:

angularjs

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).

like image 588
Adam Zerner Avatar asked Jul 22 '15 17:07

Adam Zerner


People also ask

Does Angular have digest cycle?

So, apparently, in Angular, the digest cycle doesn't run only once. Instead, it keeps running until all the variables are set to the right value. In simpler words, the digest cycle will keep running on for a finite number of times, until all the elements' values are set.

What are watchers in angular?

watchers is nothing but dirty checking, which keeps a track of the old value and new value. They are getting evaluated on each digest cycle. It can be combination of scope variable or any expression. Angular does collect all of this watchers on each digest cycle and mainatain it inside $$watchers array.

What is scope in AngularJS?

The Scope in AngularJS is the binding part between HTML (view) and JavaScript (controller) and it is a built-in object. It contains application data and objects. It is available for both the view and the controller. It is an object with available properties and methods. There are two types of scopes in Angular JS.


2 Answers

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;
};
like image 71
New Dev Avatar answered Sep 28 '22 15:09

New Dev


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.

like image 37
James Avatar answered Sep 28 '22 15:09

James