I recently started studying about digest and performance improvements in AngulaJs and found on my website that I'm using tons of ng-if.
Sometimes in ng-if there is a variable that may change, but often is fixed at the startup of the controller and then never changes.
What should I do so to improve performance avoiding digest to evaluate every loop those unchangeable ng-if? Should I change directive? With what?
E.g
In my header template I have a div that can be seen only by particular type of user. It's just a div, so I don't want to call some different template.
I put <div ng-if="userIsSuperior()"> ... </div>
When first evaluated, the return vale of userIsSuperior() never changes (during this session of course), but I know that AngularJs Digest evaluates it every loop.
How can I avoid this? Or am I missing something?
I think what you are looking for is one-time binding.
If you use:
<div ng-if="::userIsSuperior()"> ... </div>
Then the value of userIsSuperior() will only be calculated once and will stick to that value.
See demo.
First answer solutions needs AngularJS > 2.
I found a valid solutions in using OnceJS, library for one-time-binding.
Here
Related
Is there any case when I don't need an ngCloak directive?
It seems that every tag that somehow uses Angular library needs to define this directive either via ng-cloak attribute or class="ng-cloak".
Is there any downside of using this directive for every possible element?
ng-cloak will cause your element not to render straight away. Often elements will look fine without ng-cloak, in which case it would be strange to opt for a blank space.
This will often be the case if you are not using the {{x}} syntax. Perhaps you could use ng-bind instead.
Lots of elements will not be in the DOM at page load. For example, the template of a directive will obviously not be available until after the digest cycle has already run. It would be strange to try to cloak something which was not visible in the first place.
Using ng-cloak more than you need to will just hurt performance.
Adding multiple time ng-if with same variable will cost more watcher?
I mean will it be a performance issue if I use as follows?
<span ng-if="contentLoaded">{{::MY_CONTENT_1}}</span>
<span ng-if="contentLoaded">{{::MY_CONTENT_2}}</span>
Or is there any good approach to handle it?
EDIT
It seems that #Jon Quarfoth is right :
"Unless there's some kind of smart deduping going on under the covers that I don't know about, I'm pretty sure that each ng-if creates a watch.
A quick look at the source shows a $scope.$watch being created in the ngIf directive's link function:
http://github.com/angular/angular.js/blob/master/src/ng/directive/…"
Ok so I have read in several places that using ng-bind is better for performance.
But looking at these jsperfs makes me a bit confused :)
https://jsperf.com/angular-bind-vs-brackets
http://jsperf.com/ng-bind-vs-brackets/14
So what is the best way when it comes to performance?
{{::value}}
or
<div ng-bind="value"></div>
You should use ng-bind. Its a directive that puts a watcher on that variable so it only updates when the variable changes, while {{}} will dirty-check and refreshes the variable in every digest cycle.
See this answer
Also :: is called "bindonce" and will only set the variable once and wont update afterwards.
e: The jsperf tests binding from variable to html (I think), while the linked answer focuses on the behaviour afterwards. If you got 100 curly braces and you update one model, every {{}} gets updated. While ng-bind only updates if the variable itself changes, because it creates a watcher for that variable.
When it comes to one-time-binding, then you should also use the colon in ng-bind as well.
So use ng-bind="::value"
for filter or expressions you have to use brackets: ng-bind="::(value | number:2)"
use ng-bind is better . If javascript files is not loaded , {{ }} will show on the page .
I have multiple custom directives in my ngApp. The demo code is:
<top-nav></top-nav>
<left-sidebar></left-sidebar>
<div class="content">
....
</div>
The height of the 'left-sidebar' needs to be adjusted according to the height of the 'top-nav'.
Similarly there are other interdependent UI tasks. I want to run the post-load code (say app.initializeUI();) only after ALL the directives have been loaded and rendered.
Now, How do I achieve that?
EDIT :
Currently I am using the following code :
App.run(function($timeout){
$timeout(function(){ app.init() },0);
});
This runs fine, however, I am not sure this is the perfect way of doing this.
EDIT 2:
For people who want to avoid setting styles in js - use CSS Flexbox. I find it much better than calculating heights post page load. I got a good understanding of flexbox here
I would create an attribute directive with isolated scope, set terminal=true and add it to your body tag. Then in your link function, setup a $watch - isInitialized which is initially false. In your $watch handler, call your app.init(), and then de-register the $watch, so that it is always initialized once.
Setting up a terminal directive has consequences - no other directive can run after the terminal directive on the same element. Make sure this is what you want. An alternative is to give your directive the lowest possible value so that it is compiled first, and linked last.
The important pieces to this solution are:
By adding your Directive to the body tag, you ensure that it is linked last.
By adding a $watch, you ensure that all other directives have gone through a digest cycle, so by the time your $watch handler is called, all other directives should have already rendered.
Note of caution: The digest cycle may run several times before scope changes stabalise. The above solution only runs after the first cycle, which may not be what you want if you really want the final rendering.
One major thing to keep in mind when dealing with 3rd party tools as well as external'ish DOM events in AngularJS is to use the $scope.$apply() method operation to kickstart the changes. This works amazing, but sometimes the scope itself my already be grinding through a digest (which is basically what the $apply method triggers) and calling $apply when this is going on will throw an error. So to get around this, you will have to pay attention to the $scope.$$phase flag which is set to the scope whenever a digest is going on.
So now, lets say you want to change the URL and you fire off:
$scope.$apply(function() {
$location.path('/home');
});
And this works as expected, but now lets assume that the $scope is busy doing it's thing. So instead you check for the $$phase variable and assume that your changes will be picked up:
if($scope.$$phase) {
$location.path('/home');
}
else {
$scope.$apply(function() {
$location.path('/home');
});
}
This is what I've been doing (not with the code duplication obviously) and it seems to work 100% of the time. What I am concerned about is that how does AngularJS pickup the change when the scope is midway in it's digestion?
Maybe this example is not specific enough. Lets assume something bigger instead. Imagine if you have a huge webpage which plenty of bindings and lets assume that the digestion will chew through the page linearly (I'm assuming that it does something like this with respect to priority ... In this case being whatever shows up in the DOM tree first) and update the bindings on the page from top to bottom.
<div class="binding">{{ binding1 }}</div>
<div class="binding">{{ binding2 }}</div>
<div class="binding">{{ binding3 }}</div>
<div class="binding">{{ binding4 }}</div>
<div class="binding">{{ binding5 }}</div>
<div class="binding">{{ binding6 }}</div>
<div class="binding">{{ binding7 }}</div>
<div class="binding">{{ binding8 }}</div>
Lets assume that a digestion is going on and it's somewhere near the middle of the digestion queue. Now lets try and change a binding value at the top of the page somewhere.
if($scope.$$phase) {
$scope.binding1 = 'henry';
}
Now, somehow, AngularJS picks up the change and updates the binding properly. Even though the change itself can be considered to take place earlier in the queue with respect to the HTML/DOM.
My question is that how does AngularJS manage this potential race condition? I can somewhat be comfortable if binding8 updates (since it's lower down the page), but because binding1 also updates (right away without the need to call $apply again), this makes me a bit lost. Does this mean that another digestion was dispatched somewhere in between? Or is the $scope object more magical than I expect to be? I would assume that this issue has been though of before, but since finding out about $$phase and $scope in the first place was a bit tricky then I'm assuming that this also might be something that fell through the cracks.
Any ideas?
Regarding the bindings and race conditions. $digest will loop all the watchers until there is no changes. As you can observe by adding logs to watchers/binded methods it will call each binding/watcher at least twice to be sure that there is no change and all binded values are stable. Those are just dirty checks that run until every value is resolved (that have not changed within 2 loop iterations). Hope that helps.
This is explained in AngularJS docs here: http://docs.angularjs.org/api/ng.$rootScope.Scope#$digest
NOTE: This is a copy/paste of my comment as requested by matsko.
Apply will keep calling digest untill it is sure nothing has changed. So if may have a race condition at the first call, but a second one will always compensate.
You shouldn't use that workaround anyway: if the state of your $scope in uncertain on this function, you should digest it higher on your call stack where you know if you're synchroneously processed by angular, thus no need to digest, or if you're modifying the model asynchroneously so you can digest it.
On a side note, $scope.$applyis basically a $scope.$root.$digest with error handling, so if you're updating an isolated scope within a directive you can save performances by calling $digest instead of $apply:
// Somewhere in a callback
$location.path('/home');
$scope.$digest();
PS: That's for correcting e-satis answer: $digestwill also call itself while the scope is dirty.