Every time we bind something in the UI angular insert a
$watch in a
$watch list. Imagine the $watch as something that is able to detect changes in the model it is watching
Exaple-1:
User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />
Here, we have
$scope.user, which is bound to the first input, and we have
$scope.pass, which is bound to the second one. Doing this we add two $watch to the $watch list.
Example-2:
app.controller('MainCtrl', function($scope) {
$scope.foo = "Foo";
$scope.world = "World";
});
Hello, {{ World }}
Here, even though we have two things attached to the $scope, only one is bound. So in this case we only created one $watch.
Example-3:
app.controller('MainCtrl', function($scope) {
$scope.
people = [...];
});
<ul>
<li ng-repeat="person in
people">
{{person.name}} - {{person.age}}
</li>
</ul>
How many $watch are created here? Two for each person (for name and age) in people plus one for the
ng-repeat. If we have 10 people in the list it will be (2 * 10) + 1, I.e 21 $watch's.
Everything that is bound in our UI using directives creates a
$watch.When our template is loaded, Also known as in the
linking phase,
the compiler will look for every directive and creates all the $watch that are needed.
we already know that
every binding we set has its own $watch to update the DOM when is needed
The $scope.watch() function is used to observe changes in a variable on the $scope. It accepts three parameters : expression, listener and equality object
Example -4:
app.controller('MainCtrl', function($scope) {
$scope.name = "Angular";
$scope.updated = -1;
$scope.$watch('name', function() {
$scope.updated++;
});
});
<body ng-controller="MainCtrl">
<input ng-model="name" />
Name updated: {{updated}} times.
</body>
Here the
first parameter can be a string or a function. In this case it is just a string with the name of what we want to $watch, in this case, $scope.name. The second parameter is what is going to happen when $watch says that our watched expression has changed. The first thing we have to know is that when the controller is executed and finds the $watch, it will immediately fire.
we have initialized the
$scope.updated to -1,
the $watch will run once when it is processed and it will put the $scope.updated to 0.
Example-5:
app.controller('MainCtrl', function($scope) {
$scope.name = "Angular";
$scope.updated = 0;
$scope.$watch('name', function(newValue, oldValue) {
if (newValue === oldValue) { return; } // AKA first run
$scope.updated++;
});
});
<body ng-controller="MainCtrl">
<input ng-model="
name" />
Name updated: {{updated}} times.
</body>
The second parameter of $watch receives two parameters. The new value and the old value. We can use them to skip the first run that every $watch does. Normally you don’t need to skip the first run,
Example-6:
app.controller('MainCtrl', function($scope) {
$scope.user = { name: "Fox" };
$scope.updated = 0;
$scope.$watch('user', function(newValue, oldValue) {
if (newValue === oldValue) { return; }
$scope.updated++;
});
});
<body ng-controller="MainCtrl">
<input ng-model="user.name" />
Name updated: {{updated}} times.
</body>
We want to
$watch any changes in our
$scope.user object. Same as before but using an object instead of a primitive.
Here It doesn’t work. Because the
$watch by default compares the reference of the objects. In example 4 and 5, every time we modify $scope.name it will create a new primitive, so the $watch will fire because the reference of the object is new and that is our change. In this new case, since we are watching $scope.user and then we are changing $scope.user.name, the reference of $scope.user is never changing because we are creating a new $scope.user.name every time we change the input, but the $scope.user will be always the same.
Example-7:
app.controller('MainCtrl', function($scope) {
$scope.user = { name: "Fox" };
$scope.updated = 0;
$scope.$watch('user', function(newValue, oldValue) {
if (newValue === oldValue) { return; }
$scope.updated++;
}
, true);
});
<body ng-controller="MainCtrl">
<input ng-model="user.name" />
Name updated: {{updated}} times.
</body>
Now it'll work,
we added a third parameter to the $watch which is a bool to indicate that we want to compare the value of the objects instead of the reference. And since the value of $scope.user is changing when we update the $scope.user.name the $watch will fire appropriately.
$digest loop
When the browser receives an event that can be managed by the angular context and the $digest loop will be fired.
This loop is made from two smaller loops. One processes the $evalAsync queue and the other one processes the $watch list,
The $scope.$digest() function iterates through all the watches in the $scope object, and its child $scope objects (if it has any - isolated scopes).
Hey $watch, what is your value? //Watch-1
It is 9
Alright, has it changed? ///Watch-1
No, sir.
(nothing happens with this one, so it moves to the next)
You, what is your value? //Watch-2
It is Foo.
Has it changed? //Watch-2
Yes, it was Bar.
(good, we have a DOM to be updated)
This continues until every $watch has been checked.
The above checking is called dirty-checking. Now that all the $watch have been checked there is something else to ask: Is there any $watch that has been updated? If there is at least one of them that has changed, the loop will fire again until all of the $watch report no changes. This is to ensure that every model is clean. Have in mind that if the loop runs more than 10 times, it will throw an exception to prevent infinite loops.
When the $digest loop finishes, the DOM makes the changes.
Example-4: