This is eighth part in the series of AngularJS and I hope you are enjoying this series. In last post of the series, We discussed about Data binding in AngularJS and we saw that how it is different than normal classical bindings. In this post we’ll unravel the magic behind the data binding and discuss key concepts involved.
If you want to go through the the earlier posts in this series the here are the links
Learning {{AngularJS}} with Examples–Part 1
Learning {{AngularJS}} with Examples–Part 2
Learning {{AngularJS}} with ASP.NET MVC – Part 3
Learning {{AngularJS}} with ASP.NET MVC – Part 4
Learning {{AngularJS}} with ASP.NET MVC – Part 5
Learning {{AngularJS}} with ASP.NET MVC – Part 6
DataBinding in {{AngularJS}} – Part 7
I will suggest you to go though the last part of the series (Data Binding in {{AngularJS}} – Part 7) before going through further. It will help in better understanding the topic.
Before moving directly to the concepts behind the scene, Let’s discuss prerequisites that will help in understanding the whole concept. So let’s start from the scope.
What is Scope?
Scope is the core of an Angular application. It refers to an application model and execution context. An application has single $rootscope and multiple $scope objects associated to it. $scope is accessible between view and controller and it is the only medium of communication between both. It is like a container and holds all the other required information that is used by Angular for providing other features. You’ll see some in this post.
So before moving ahead, let’s understand that how browser identifies an event and takes the necessary actions. The flow of an event handling works as
Events could be fired in many scenarios like page load, some response comes from server, mouse move, button click, timer events etc.
When we use Angular in a application, It modifies the normal JavaScript event flow and provides its own event handling model. This event handling model comes in flow when event is processed through Angular Execution context. It also provide many other features like exception handling, property watching etc. All the events that we define using angular constructs, is part of Angular Execution context. But you must have seen that when we try to use normal JavaScript or jQuery event then it does not fire. Hold On!! we will discuss it and its workaround at later part of the post.
So we have understood the basics, now lets come to the main topic and discuss how data-binding works in Angular. There are two steps involved in it.
1- Angular creates a $watch list that is available under scope.$$watchers. Every element that accessed in UI using some directive, a watch is created for that and got a place in this list.
2- Whenever a event (already discussed earlier in this post) occurs, all the watchers get iterated (also known as digest loop) and it is checked if any of the data got changed (called dirty-checking). If yes then it updates the UI or the underlying model.
Let’s understand the above points in detail. The key concepts are
1- $watch and $watchlist
2- $digest loop
3- $apply (Looking new? Got discussed in detail later section)
4- Dirty checking etc
What is $Watch list ?
It is the key of data binding and as discussed above that when a item is accessed in UI via angular directive, an entry gets created in the watch list for that item. Now can you imagine the number of watchers in an application? It could be way high than what you think. We’ll see that in coming section. Let’s see some examples
1- $Watch gets created for elements that are bound to UI. Say I have created some properties as
JS (controller)-
appModule.controller('myController', function ($scope) { $scope.message1 = 'message1'; $scope.message2 = 'message2'; $scope.helloMessage = "Hello"; });
HTML (View)-
<div ng-controller="myController"> Message - {{helloMessage}} </div>
So here only one entry will be created in the watch-list as only helloMessage is accessed in UI. Other properties like message1, message2 that are part of $scope but watchers would not be created for those as there are not accessed in UI.
2- For each ng-model attribute, one item gets created in watch-list. As
<input type="text" ng-model="talkName" /> <input type="text" ng-model="speakerName" />
Here, we have two input controls with ng-model as talkName and speakerName. For both, there will be an entry get created in watch list. $scope.talkName and $scope.speakerName are bound to first and second input respectively.
3- We have used ng-repeat in our earlier post, which renders the elements in UI based on the available list of items. How many watchers will be created in that case. Let’s take example from the last post, where I have shown the list of talks on UI by using ng-repeat.
JS –
eventModule.controller("eventController", function ($scope, EventsService) { EventsService.getTalks().then(function (talks) { $scope.talks = talks }, function () { alert('error while fetching talks from server') }) });
Here Talks comes from server which contains (Say six items) in the list.
HTML:
<div class="row"> <table class="table table-condensed table-hover"> <tr ng-repeat="talk in talks"> <td> {{talk.id}}</td> <td> {{talk.name}}</td> <td> {{talk.speaker}}</td> <td> {{talk.venue}}</td> <td> {{talk.duration}}</td> </tr> </table> </div>
How many watchers get created for the above?
It would be 5*6 = 30. (Properties bound in UI * number of items in the list ).
As I mentioned in my post earlier, number of watchers could be very high because if you have used many ng-repeat in your page then it would grow exponentially.
4- We can also create the watch programmatically. Let’s see the example
HTML :
<div ng-controller="myController"> <input type="button" value="UpdateHelloMessage" ng-click="updateHelloMessage()" /> {{message1}} </div>
JS:
appModule.controller('myController', function ($scope) { $scope.message1 = 'Test Message'; $scope.helloMessage = "Hello"; $scope.updateHelloMessage= function () { $scope.helloMessage = "Hellow World!!"; console.log($scope.helloMessage); }; $scope.$watch('helloMessage', function (newVal, oldVal) { if(newVal != oldVal) $scope.message1 = "Hello message updated"; }); });
If we see the HTML then we’ll say that there are only one watch (for message1) in this app. But it has two as I had added a watch on $scope.helloMessage programmatically. When we click on button UpdateHelloMessage, $scope.message1 also get updated due to new watch created and gets reflected on UI.
Before moving to next topic, let’s understand when does an entry get created and added in watch list.
When does an entry get added in the watch list?
Angular uses it’s own compiler which traverses the UI to get all the angular directives used. This is how angular directives used in the HTML are understood and accordingly rendered in UI. Angular process can be depicted as
As we can see there are two main steps involved : Compile and Linking.
1- Compile process – It traverses the DOM and collects all the directives and passes it to the linking function.
2- Linking – It actually combines all the directives and assigns to a scope. It sets up $watch expression for each directive. $watch actually responsible to notify the directive in case of a change in property of $scope, then it get reflected in UI. Similarly, if any changes occurs in UI as by some user interaction, it get reflected in model. Here one point to be noted that both controller and directive has the ability to access the scope but not each other.
So here we can understand the entries in watch list, get created in linking phase. And this is where it knows the number of watch need to be created in case of ng-repeat. We’ll see some examples later.
$digest loop
Earlier in this post, I mentioned that whenever an event occurs on browser, digest loop gets fired. It contains two smaller loops, one processes $evalAsync queue and other iterates the watch list for each item, to detect the changes. Then, it finds all the items which got changed and UI gets updated accordingly.
Note – The $evalAsync queue is used to schedule work which needs to occur outside of current stack frame, but before the browser’s view render. It provides the similar features provided by setTimeout().
What is dirty checking?
The process of checking every watch to detect the changes, is called dirty checking. There could be two scenarios
First –
Second –
In second case, loop continues till it finds no changes in the entire loop. And once it completes, DOM gets updated if required.
So till now you must have understood that magic of data binding occurs in AngularJS. We had one more key item left apply that we didn’t discuss in detail. Whats the use of that?
$apply – It actually plays pivotal role in the whole process. Angular does not trigger the digest loop directly, it has to be triggered via $apply call and this method is responsible to enter the execution in Angular Execution context. Only model modifications which execute inside the $apply method is properly accounted for by Angular. So now question arises that even we did not call the $apply in any earlier examples still it is working as expected. Actually Angular does it for us. Angular wraps every event with apply so that digest loop gets fired.
What are other uses of apply method?
As we have already seen that angular wraps every event with apply that fires digest loop. It means if we want to run the digest loop in some scenarios then we need to execute the apply method. You must have seen many times that whenever we make some changes using JavaScript or jQuery methods it is not reflected in Angular. In those scenarios apply method provides entry in Angular Execution context. Let’s see it with an example.
HTML :
<div ng-controller="myController"> <input type="button" value="UpdateHelloMessage" ng-click="updateHelloMessage()" /> {{helloMessage}} </div>
JS :
appModule.controller('myController', function ($scope) { $scope.helloMessage = "Hello"; $scope.updateHelloMessage = function () { setTimeout(function() { $scope.helloMessage = "Hello World!!"; }, 0); }; });
So what do you think about the above code? Would it work as expected? I mean when you click on the UpdateHelloMessage button, updated $scope.helloMessage (Hello World!!) would reflect on UI.
No It wont work.
Although the model $scope.helloMessage will get updated but it wont appear on UI because digest loop wont run in this case. But why digest loop wont run? Because the value is updated in setTimeout method which is java-script method and it won’t run in Angular context. So to get it running as expected we need to call $scope.$apply() and that would trigger digest loop and updated value will be shown on UI. So we can update the JS as
appModule.controller('myController', function ($scope) { $scope.helloMessage = "Hello"; $scope.updateHelloMessage = function () { setTimeout(function() { $scope.helloMessage = "Hello World!!"; $scope.$apply(); }, 0); }; });
Now it will work as expected. Now you must have understand that there is no magic but a simple logic is written to do that.
Hope you have enjoyed this post. Please do share your feedback and let me know if you want to see any specific topic here.
To navigate to previous and next post of the series, use the links at the bottom of the post.
Happy Learning
Brij
Thanks for the tutorial. would you be able to fix the code snippets, it is unreadable!
Thanks for pointing out. Somehow it got screwed up at 11th hour. Fixed that. Thanks again
Pingback: DataBinding in {{AngularJS}} – Part 7 | Code Wala
good tutorial
Glad you liked it!!