What is considered a watcher in Angular?
Asked Answered
F

3

8

What are considered "watchers" in Angular? Are watchers themselves the only type of watchers, or are other Angular constructs such as ngModel watchers as well?

Or am I missing the big picture? For example, are watchers what enable directives like ngModel to work?

update: Is there a way to tell when there exists watchers? In testing I want to know when to call scope.$digest()

Falsecard answered 3/8, 2015 at 17:49 Comment(2)
I highly recommend watching this talk from ng-conf called "Sasqatch is Real". It talks about everything that creates a watch and shows just how many watches your normal view could end up having and the impact on having too many watches.Fluoroscopy
Thanks for the link -- seems to be what I'm looking for.Falsecard
F
2

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. you could look at how many watchers are there by doing console.log($scope.$watchers) inside your controller.

Markup

<body class="container" ng-controller="myCtrl">
  Hello {{test}}
  I'm test to show $$watchers {{test1}}
  <div ng-show="show">SHowiing this div using ng-show</div>
  <div ng-if="show">SHowiing this div using ng-show</div>
  Watcher is not compulsary that should scope variables {{'1'}}
</body>

Plunkr

Guess in above code how many watchers are there, as you can see there are 3 {{}} interpolation directive that are going to place in watchers array and then if you see in console $scope.$$watchers it will show 5 watchers.

How come there it shows 5 watchers. As we can only see 3, Actually we've used ng-show and ng-if directive which internally place $watch on the expression provided in its attribute value. & those expression gets evaluated on each digest cycle.

You could also create your custom watcher by using $watch(deep/simple watch) & $watchGroup

Also you could have watcher use $attrs.$observe, this does work same as the watch but the only special thing it does it, it works for the interpolation variable.

$attrs.$observe('test',function(value){
    alert('')
});

Most of angular directive internally uses watcher like ng-repeat, ng-show, ng-if, ng-include, ng-switch, ng-bind,interpolation directive {{}}, filters, etc. They put watch internally to manage two way binding thing.

Freddiefreddy answered 3/8, 2015 at 18:18 Comment(2)
So I guess it's not possible to expect watchers in only a certain set of places. I.e. it's not always apparent where watchers show up. I wonder if there is a list of places (in which angular directives) one can expect watchers.Falsecard
@Gnuey could you look at the updated answer please...Thanks :)Freddiefreddy
C
1

Watchers (if we take only the documentation you are based on) are angular mechanisms aiming to observe in a two-way binding style a variable or a function result during any Angular digest cycle; and no matter what the triggering event of the digest cycle would be.

I would call a "watcher" any Angular mechanism that are able to trigger some codes based on ANY event that could occur.

Typically, to create a Watcher, you should use: $scope.watch(...)

Just be aware that it's better to avoid watchers as long as we can.
Indeed, their callback would be triggered at EACH digest cycle to perform dirty checking; often impacting performance.

ng-model is not linked with the concept of watcher.
ng-model is just a way to bind some variable from view to controller.
They are two distinct concepts.

Cloth answered 3/8, 2015 at 18:3 Comment(2)
Is there a way to tell when there exists watchers? In testing I want to know when to call scope.$digest()Falsecard
In Angular, you have to manage yourself the role of digest when dealing with testing. Thus, digest is thrown at Promise side generally. When you want to resolve a promise, you have to call a digest. Also, when you want to manually set a viewValue on a form component (like input), running a $scope.$digest() would allow to apply the binding. Any kind of event that would trigger a digest, you have to play it manually.Cloth
B
1

To illustrate how $watch(), $digest() and $apply() works, look at this example:

<div ng-controller="myController">
{{data.time}}

<br/>
<button ng-click="updateTime()">update time - ng-click</button>
<button id="updateTimeButton"  >update time</button>
</div>
<script>
var module = angular.module("myapp", []);
var myController1 = module.controller("myController", function($scope) {
    $scope.data = { time : new Date() };
    $scope.updateTime = function() {
        $scope.data.time = new Date();
    }
    document.getElementById("updateTimeButton")
            .addEventListener('click', function() {
        console.log("update time clicked");
        $scope.data.time = new Date();
    });
});
</script>

This example binds the $scope.data.time variable to an interpolation directive which merges the variable value into the HTML page. This binding creates a watch internally on the $scope.data.time variable.

The example also contains two buttons. The first button has an ng-click listener attached to it. When that button is clicked the $scope.updateTime() function is called, and after that AngularJS calls $scope.$digest() so that data bindings are updated.

The second button gets a standard JavaScript event listener attached to it from inside the controller function. When the second button is clicked that listener function is executed. As you can see, the listener functions for both buttons do almost the same, but when the second button's listener function is called, the data binding is not updated. That is because the $scope.$digest() is not called after the second button's event listener is executed. Thus, if you click the second button the time is updated in the $scope.data.time variable, but the new time is never displayed.

To fix that we can add a $scope.$digest() call to the last line of the button event listener, like this:

document.getElementById("updateTimeButton")
    .addEventListener('click', function() {
console.log("update time clicked");
$scope.data.time = new Date();
$scope.$digest();
});

Instead of calling $digest() inside the button listener function you could also have used the $apply() function like this:

document.getElementById("updateTimeButton")
    .addEventListener('click', function() {
$scope.$apply(function() {
    console.log("update time clicked");
    $scope.data.time = new Date();
});
});

Notice how the $scope.$apply() function is called from inside the button event listener, and how the update of the $scope.data.time variable is performed inside the function passed as parameter to the $apply() function. When the $apply() function call finishes AngularJS calls $digest() internally, so all data bindings are updated.

Brookes answered 1/2, 2017 at 11:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.