Monday 12 August 2019

javascript - How does data binding work in AngularJS?

By dirty checking the $scope object


Angular maintains a simple array of watchers in the $scope objects. If you inspect any $scope you will find that it contains an array called $$watchers.


Each watcher is an object that contains among other things



  1. An expression which the watcher is monitoring. This might just be an attribute name, or something more complicated.

  2. A last known value of the expression. This can be checked against the current computed value of the expression. If the values differ the watcher will trigger the function and mark the $scope as dirty.

  3. A function which will be executed if the watcher is dirty.


How watchers are defined


There are many different ways of defining a watcher in AngularJS.



  • You can explicitly $watch an attribute on $scope.


    $scope.$watch('person.username', validateUnique);

  • You can place a {{}} interpolation in your template (a watcher will be created for you on the current $scope).


    username: {{person.username}}



  • You can ask a directive such as ng-model to define the watcher for you.





The $digest cycle checks all watchers against their last value


When we interact with AngularJS through the normal channels (ng-model, ng-repeat, etc) a digest cycle will be triggered by the directive.


A digest cycle is a depth-first traversal of $scope and all its children. For each $scope object, we iterate over its $$watchers array and evaluate all the expressions. If the new expression value is different from the last known value, the watcher's function is called. This function might recompile part of the DOM, recompute a value on $scope, trigger an AJAX request, anything you need it to do.


Every scope is traversed and every watch expression evaluated and checked against the last value.


If a watcher is triggered, the $scope is dirty


If a watcher is triggered, the app knows something has changed, and the $scope is marked as dirty.


Watcher functions can change other attributes on $scope or on a parent $scope. If one $watcher function has been triggered, we can't guarantee that our other $scopes are still clean, and so we execute the entire digest cycle again.


This is because AngularJS has two-way binding, so data can be passed back up the $scope tree. We may change a value on a higher $scope that has already been digested. Perhaps we change a value on the $rootScope.


If the $digest is dirty, we execute the entire $digest cycle again


We continually loop through the $digest cycle until either the digest cycle comes up clean (all $watch expressions have the same value as they had in the previous cycle), or we reach the digest limit. By default, this limit is set at 10.


If we reach the digest limit AngularJS will raise an error in the console:


10 $digest() iterations reached. Aborting!

The digest is hard on the machine but easy on the developer


As you can see, every time something changes in an AngularJS app, AngularJS will check every single watcher in the $scope hierarchy to see how to respond. For a developer this is a massive productivity boon, as you now need to write almost no wiring code, AngularJS will just notice if a value has changed, and make the rest of the app consistent with the change.


From the perspective of the machine though this is wildly inefficient and will slow our app down if we create too many watchers. Misko has quoted a figure of about 4000 watchers before your app will feel slow on older browsers.


This limit is easy to reach if you ng-repeat over a large JSON array for example. You can mitigate against this using features like one-time binding to compile a template without creating watchers.


How to avoid creating too many watchers


Each time your user interacts with your app, every single watcher in your app will be evaluated at least once. A big part of optimising an AngularJS app is reducing the number of watchers in your $scope tree. One easy way to do this is with one time binding.


If you have data which will rarely change, you can bind it only once using the :: syntax, like so:


{{::person.username}}



or




The binding will only be triggered when the containing template is rendered and the data loaded into $scope.


This is especially important when you have an ng-repeat with many items.



{{::person.username}}

No comments:

Post a Comment

php - file_get_contents shows unexpected output while reading a file

I want to output an inline jpg image as a base64 encoded string, however when I do this : $contents = file_get_contents($filename); print &q...