I want to execute some function on every $digest cycle.
The documentation for $scope reads:
If you want to be notified whenever $digest is called, you can
register a watchExpression function with no listener. (Since
watchExpression can execute multiple times per $digest cycle when a
change is detected, be prepared for multiple calls to your listener.)
So does that mean it can be accomplished this way:
$rootScope.$watch(function () {
// Cron job
});
I'm not sure if every $digest cycle hits the $rootScope, or is it even emitted from $rootScope. I only know there's only 1 $digest loop and it occurs every time a $watch detects changes.
Q: So does that mean it can be accomplished this way?
A: Yes, although some directives/services might choose to only digest their local scope to improve performance.
That said it's recommended to execute as less code as possible in a digest loop.
digest isn't like a cron job, it doesn't run automatically every x seconds.
It runs when you call $scope.$apply() or when an event like ng-click or $http.get occurs.
Related
I was contemplating a means to increase performance in an app while meeting client demands. Right now it seems that the digest runs 5x per second where we only really need it to run perhaps twice per second.
Is there a way to lower the amount of times the digest runs?
In AngularJS there are a set of scenarios when a digest cycle kicks in, some of them are, whenever a value bind to the $Scope or $rootScope object changes(like $scope.text or $rootScope.text), DOM Events (like ng-click, ng-bind etc..), Ajax with callbacks ($http etc..), Timers with callbacks ($timeout, setTimeout etc..), calling $apply, $digest etc..
PFA:
To do:
If you want to reduce the number of digest cycles triggered you'll have to look into each of these above-listed points. Like you can reduce the number of $watchers by using one-time binding (eg: {{::myProperty}} - documentation), limiting the cases of programmatically triggering $apply ($scope.$apply), and replacing $timeouts with $scope.$evalAsync() (because $scope.$evalAsync() will try to trigger in the same digest loop itself whereas $timeout() will wait for the current digest cycle to be done) or un registering the watchers once you found the change and no longer required to watch again like this,
var unregister = $scope.$watch('foo', function () {
// Do something here ...
unregister();
});
etc..
This is probably not the answer you are looking for, however I don't think the digest cycle is the performance killer.
Act upon changes is what may be causing performance issues.
A quick way to avoid the digest cycles acting upon updates is to cache the result of functions instead of binding functions to the HTML templates.
Eg: ng-show="shouldShow()" will be evaluated every time by the digest cycle. If you are able to cache the result of this function in a JS variable in the Controller then use the cached result, you may see performance improvements.
eg: $scope.show = shouldShow(), then ng-show="show"
As i was going through the life cycle of scope, I came across $digest should be called by $apply. But I want to know if possible can we enable $digest without $apply. If yes what is disadvantage
when you calls the $scope.$apply() function, it call the $rootScope.$digest(). So as a result of that, digest cycle starts from the rootScope and call all the child scopes.
you can call the digest using $scope.$digest() but this will start the cycle for child scope only. sometimes binding will not properly occur because digest cycle is not starting from the root scope.
you can check this article to get an idea about how the digest cycle works
The $scope.$digest() function iterates through all the watches in the $scope object, and its child $scope objects (if it has any). When $digest() iterates over the watches, it calls the value function for each watch.
The $scope.$apply() function takes a function as parameter which is executed, and after that $scope.$digest() is called internally.
It seems that one can achieve the same result using two different approaches:
doSomething();
$scope.$digest();
or
$scope.$apply(function() {
doSomething();
});
So what are the differences and what to use when?
AngularJS $digest
Processes all of the watchers of the current scope and its children. Because a watcher's listener can change the model, the $digest() keeps calling the watchers until no more listeners are firing. This means that it is possible to get into an infinite loop. This function will throw 'Maximum iteration limit exceeded.' if the number of iterations exceeds 10.
Usually, you don't call $digest() directly in controllers or in directives. Instead, you should call $apply() (typically from within a directive), which will force a $digest().
If you want to be notified whenever $digest() is called, you can register a watchExpression function with $watch() with no listener.
In unit tests, you may need to call $digest() to simulate the scope life cycle.
Both will do the same thing. But, using $scope.apply() wrapped as a function is best practice. Why because, when you wrap something in $scope.apply(), you can write catch block for that. So, you can catch any exceptions you may face.
You can check if a $digest is already in progress by checking $scope.$$phase.
if(!$scope.$$phase) {
//use anyone yours: $digest or $apply
}
scope.$digest() will fire watchers on the current scope, and on all of its children, too. scope.$apply will evaluate passed function and run $rootScope.$digest().
See this: $apply vs $digest in directive testing
$scope.$digest() : This is cycle process. So when we call $scope.$digest() it will start a digest cycle and check all the watchers of the scope whatever should changed will be changed, and after that a dirty check will be processed , which will check while the digest cycle was is progress is anything changed , if changed then again the digest cycle will start working.One Should not call $scope.$digest() manually because whenever anything change in the scope it will be called . More details here $digest
$scope.$apply: In $scope.$apply you implicitly tell that only watchers affected by the function will be update or only specific watchers will be checked for the update,it handles exception internally , no manual handling needed . More details $apply
I am a little confused about how $scope.$apply and digest loops function. From what I understand, since digest loops runs at regular intervals and not always, we can force the digest loop to run on certain scope variables which we want to update immediately. Also in the description here, it's given that $scope.$apply should be used when an async call is made, so that variables can be updated. My doubt is if digest loop doesn't run always, how are scope variables almost instantaneously updated in the view/controller?
Simply, use $scope.$apply() whenever you're outside the angular scope. For example within setTimeout function, as it is outside the world of angular.
Is there any way to call custom function in angular after angular finishes all watch cycle.
Requirement
I have multiple watch functions inside my controller. Now I want to execute a function only after all watch functions are executed by angular
There are couple of ways to do register a callback once a digest is completed.
Using $$postDigest:
$scope.$$postDigest fires a callback after the current $digest cycle completed.
However this runs only once after the next digest cycle. To make it run after each digest cycle run it along with $watch. This is based on the code sample given here
var hasRegistered = false;
$scope.$watch(function() {
if (hasRegistered) return;
hasRegistered = true;
$scope.$$postDigest(function() {
hasRegistered = false;
fn();
});
});
The $watch can get triggered multiple times during a digest cycle so we use a flag hasRegistered to prevent $$postDigest callback to be registered multiple times.
Note: $$postDigest will not trigger another digest cycle. So any modifcation to $scope inside $$postDigest will not get reflected in the dom. $$ means this is a private function in angularjs, so the function is not stable and may change in the future.
Using $timeout:
$timeout(function(){
console.log("Running after the digest cycle");
},0,false);
This runs after the current digest cycle is complete.
Note: The third argument is set to false to prevent another digest cycle trigger.