Last Updated: September 09, 2019
·
862
· phantom

Angularjs model design pitfall

What are bindings ?

Every {{bind}} is parsed internally by angular witch create an $watch expression which is used by scope on $digest loop to update the view and that’s a “simple operation”. As well whenever you create your own $watch expression in controller or directive and whenever you do something expensive there browser will need more time to update view because remember is synchronous operation and since we have only one thread in browser the invocation of other stuff is completely blocked since it needs to finish previous invocation.

What you should avoid when you use scope events ($emit, $broadcast) ?

You should not fire scope events inside of your $watcher because bindings will be paused.
Even if you fire event $emit (up tree) or $broadcast (down tree) outside $watcher that will not prevent digest loop to be faster because that create another traversal loop and if that scope $emit or $broadcast loop is expensive it will pause digest because again its synchronous operation.

What about $watchers, $digest?

$watchers are fired in decremental order and all your custom $watchers are going to be invoked after the binding of the template (template watchers remember {{bind}} expresions) witch is the good part but angular use an depth-first traversal loop in which thy don’t check if parent scope is already $watched instead of that thy trigger $watcher loop again on that parent scope.

This is the reason why you cannot do the:

$scope.value = 1;
$scope.$watch(‘value’, function(){
     $scope.value +=1;
});

You get message “{0} $digest() iterations reached. Aborting! Watchers fired in the last 5 iterations: {1}”;

Which means that each parent scope is dirty checked at least twice instead of ontime dirty checking per scope. Question is : is this done intentionally or accidentally ? If is intentionally than is by my opinion bad design because we come up to the fact this is an synchronous operation. Because each time this happen angular update view which is causing a DOMTree mutation event.

What about scope.$apply?

What triggers scope $apply?
Asynchronous operations.
Every native async event eg. (ng-click, ng-mousedown) . Built-in directives eg ng-model, ng-options, Services eg $http, $timeout, $interval, $location, $q, $browser.defer, scope.$evalAsync

Why is this bad ?

On large angularjs applications (150k loc of code) digest time is growing proportionally with your scope tree. As you can see in graph image the total time of Scope.$apply 1.63s which is really a lot. The reason for this is that the tree structure is to large and it contains a lot of data. When you have complex structure which contains a lot of data it takes time to update a ui view.
Because each Scope.$apply runs scope tree traversal from root scope to the deepest child scope. And number of bindings to template is proportional with the growth of the time to update the view because it’s synchronous operation.
And if you have application which is using event sourcing to deliver data to client and if multiple events delivering data to your browser at same time that cause multiple digest loops which is again synchronous operation.

If you ask me now will I suggest you to use angular with large client application structure ?

No I don’t suggest you to use it. Even if you decuple everything and you use amd to load all stuff It will not help you to solve the problem because of the digest loop.!