Angularjs performance when binding to deeply nested object attributes - javascript

When data binding in Angularjs, is there a performance difference (significant or otherwise) between
<div>{{bar}}</div>
and
<div>{{foo.bar}}</div>?
What about <div>{{foo.bar.baz.qux}}</div>?
Context: I am on a team building a large Angularjs application that will potentially have a high volume of data flowing through it, and we would like to avoid the performance hit if there is one.

As i know, the re-evaluation happens within a digest.
Angular iterates through all values in the scope and checks, if the value has changed.
It doesn't look like deep nesting causes much pains there, cuz it's just checking agains the value used in the view. (as long as you dont place a watcher on this deep nested object)
But for some hints:
Don't use methods for conditions within the view:
<span data-ng-hide="someFunction()"></span>
The function will be executed on each digest this may hurt.
Don't use watchers on top of a deep object structure:
Will recursivly run through the whole thing for re-evaluation --> hurts
Use directives instead of {{}}:
Why? Example: angular-translate:
If provides a filter and a directive for the same thing.
<span>{{'WELCOME'|translate}}</span>
<span data-translate="'WELCOME'"></span>
The filter will be re-evaluated on every digest, while the directive has a watcher on that passed value and only re-evaluates, if this code does actually change.
Use data-ng-if instead of ng-Show/Hide (And since the data-ng-if is available):
ng-Show/Hide just makes the DOM elements disappear by using display:none; with css.
The hidden DOM elements will still be evaluated and the data changed, even if it's not visible.
ng-if will completely remove the DOM element, no re-evaluation for stuff within the ng-if

Related

AngularJS suspend $watch for internal updates

I have written a directive which has a two way binding to bring in the text specification for a flow chart which is used to generate the actual objects (steps and connections) inside the directive. I have a $watch set up inside the directive to allow the controller to send in a new specification (e.g. loading a new chart), but I also want to have the directive make changes to the specification in response to user action (e.g. deleting a connection) so that the controller can save the changed specification.
I have a function in the directive which converts the chart objects back into a text specification, but if I simply replace the specification scope variable with the updated value, the original watch sees a change (which it thinks might have come from the controller) and so reloads the chart from the specification. This has the effect of breaking things like dragging elements around because the elements are being removed from the DOM and replaced by new ones.
What I would like to be able to do is temporarily suspend the $watch while I make my internal changes, or in some other way avoid the watch from triggering when the directive makes changes to it, only going off when the controller makes changes from outside. I tried to unbind and rebind the watch around making the change, but because the actual checking happens elsewhere in the cycle that does not work. An alternative solution I could use is to have two variables passed between the controller and directive, one going each way, but that is somewhat inelegant. Any better suggestions would be welcome.
When I do this, usually my $watch looks something like this:
$scope.$watch('MyVar',function(newval,oldval) {
if (oldval == newval) return;
if (newval == $scope.internalval) return;
// process here
})
For precisely the reason you outline. I don't believe you can turn the $watch off, so before I make an internal update to the watched variable, I update a tracking version of the variable to make sure I don't get infinitely-recursing changes. It seems like a pain, but the watched variables are finite and I always use a setter function so the code updating the internal value is only written once

Callback when ngRepeat/ngIf mutates DOM

I have a directive that needs to execute a callback function whenever its DOM subtree is mutated (by ngIf or ngRepeat for instance).
The directive is ideally able to be easily inserted to templates I have already made, which rules out putting an ng-init. I've looked at the documentation, and neither ngRepeat or ngIf seem to have any events. Additionally, it seems that most of the browser DOM events have been depreciated as well.
I would use a watch, but I can't think of an expression that will work, as jQuery returns a new object every time and the length of .children() might be unchanged through mutation if ngRepeat removes and inserts a node in the same $digest.
Any suggestions on how to detect any DOM subtree mutation entirely from a template-less directive?
EDIT: For more detail, I have multiple tables that have rows of data inserted with ngRepeat. Whenever a row is inserted, if the table has a resize directive, I need to add CSS to it. The resize directive is general enough to go on every table without needing any input, so I would prefer to not have to add ngInits to all the ngRepeat elements.
For anyone else looking for a solution that will usually work in situations like this, Angular recompiles all the repeated elements, even if they were already present. Thus the actual DOMNode objects are different, so you can just watch a DOMNode.

How to reload angularjs scope data without rerendering bound view

I've got a small problem. I have a bootstrap tabset within an angularjs app. The tabs are partially generated from ng-repeat and contain data, that is bound to the scope.
Basically, within the controller:
$scope.data = { ... } // Loaded from a factory
Now, when I reload this data (by replacing the old one with the new one) the tabset will be rebuilt by ng-repeat and the view will automatically switch to the first tab.
Is there a way to replace the data in scope without rebuilding everything in the view?
You aren't explicit about the specific data and which of it is used in what ways.
So this will have to be a general answer:
Try to update only the bits that actually changed.
For example do not replace whole objects or arrays, but only updated properties or indices that actually changed. That way only the relevant parts of the GUI will update.
If you must replace objects, you can still help angular keep the connection between objects in the model and dom elements for ng-repeat if you use "track by" in the expression (which is possible only if the element has some unique id that you can use for that).
Another option: Use one time binding for the parts that should change only exactly once when the data is first loaded: See the section "One-time binding" in https://docs.angularjs.org/guide/expression

When to favor ng-if vs. ng-show/ng-hide?

I understand that ng-show and ng-hide affect the class set on an element and that ng-if controls whether an element is rendered as part of the DOM.
Are there guidelines on choosing ng-if over ng-show/ng-hide or vice-versa?
Depends on your use case but to summarise the difference:
ng-if will remove elements from DOM. This means that all your handlers or anything else attached to those elements will be lost. For example, if you bound a click handler to one of child elements, when ng-if evaluates to false, that element will be removed from DOM and your click handler will not work any more, even after ng-if later evaluates to true and displays the element. You will need to reattach the handler.
ng-show/ng-hide does not remove the elements from DOM. It uses CSS styles to hide/show elements (note: you might need to add your own classes). This way your handlers that were attached to children will not be lost.
ng-if creates a child scope while ng-show/ng-hide does not
Elements that are not in the DOM have less performance impact and your web app might appear to be faster when using ng-if compared to ng-show/ng-hide. In my experience, the difference is negligible. Animations are possible when using both ng-show/ng-hide and ng-if, with examples for both in the Angular documentation.
Ultimately, the question you need to answer is whether you can remove element from DOM or not?
See here for a CodePen that demonstrates the difference in how ng-if/ng-show work, DOM-wise.
#markovuksanovic has answered the question well. But I'd come at it from another perspective: I'd always use ng-if and get those elements out of DOM, unless:
you for some reason need the data-bindings and $watch-es on your elements to remain active while they're invisible. Forms might be a good case for this, if you want to be able to check validity on inputs that aren't currently visible, in order to determine whether the whole form is valid.
You're using some really elaborate stateful logic with conditional event handlers, as mentioned above. That said, if you find yourself manually attaching and detaching handlers, such that you're losing important state when you use ng-if, ask yourself whether that state would be better represented in a data model, and the handlers applied conditionally by directives whenever the element is rendered. Put another way, the presence/absence of handlers is a form of state data. Get that data out of the DOM, and into a model. The presence/absence of the handlers should be determined by the data, and thus easy to recreate.
Angular is written really well. It's fast, considering what it does. But what it does is a whole bunch of magic that makes hard things (like 2-way data-binding) look trivially easy. Making all those things look easy entails some performance overhead. You might be shocked to realize how many hundreds or thousands of times a setter function gets evaluated during the $digest cycle on a hunk of DOM that nobody's even looking at. And then you realize you've got dozens or hundreds of invisible elements all doing the same thing...
Desktops may indeed be powerful enough to render most JS execution-speed issues moot. But if you're developing for mobile, using ng-if whenever humanly possible should be a no-brainer. JS speed still matters on mobile processors. Using ng-if is a very easy way to get potentially-significant optimization at very, very low cost.
From my experience:
1) If your page has a toggle that uses ng-if/ng-show to show/hide something, ng-if causes more of a browser delay (slower). For example: if you have a button used to toggle between two views, ng-show seems to be faster.
2) ng-if will create/destroy scope when it evaluates to true/false. If you have a controller attached to the ng-if, that controller code will get executed every time the ng-if evaluates to true. If you are using ng-show, the controller code only gets executed once. So if you have a button that toggles between multiple views, using ng-if and ng-show would make a huge difference in how you write your controller code.
The answer is not simple:
It depends on the target machines (mobile vs desktop), it depends on the nature of your data, the browser, the OS, the hardware it runs on... you will need to benchmark if you really want to know.
It is mostly a memory vs computation problem ... as with most performance issues the difference can become significant with repeated elements (n) like lists, especially when nested (n x n, or worse) and also what kind of computations you run inside these elements:
ng-show: If those optional elements are often present (dense), like say 90% of the
time, it may be faster to have them ready and only show/hide them, especially if their content is cheap (just plain text, nothing to compute or load). This consumes memory as it fills the DOM with hidden elements, but just show/hide something which already exists is likely to be a cheap operation for the browser.
ng-if: If on the contrary elements are likely not to be shown (sparse) just build them and destroy them in real time, especially if their content is expensive to get (computations/sorted/filtered, images, generated images). This is ideal for rare or 'on-demand' elements, it saves memory in terms of not filling the DOM but can cost a lot of computation (creating/destroying elements) and bandwidth (getting remote content). It also depends on how much you compute in the view (filtering/sorting) vs what you already have in the model (pre-sorted/pre-filtered data).
One important note:
ngIf (unlike ngShow) usually creates child scopes that may produce unexpected results.
I had an issue related to this and I've spent MUCH time to figure out what was going on.
(My directive was writing its model values to the wrong scope.)
So, to save your hair just use ngShow unless you run too slow.
The performance difference is barely noticable anyway and I am not sure yet on who's favour is it without a test...
If you use ng-show or ng-hide the content (eg. thumbnails from server) will be loaded irrespective of the value of expression but will be displayed based on the value of the expression.
If you use ng-if the content will be loaded only if the expression of the ng-if evaluates to truthy.
Using ng-if is a good idea in a situation where you are going to load data or images from the server and show those only depending on users interaction. This way your page load will not be blocked by unnecessary nw intensive tasks.
ng-if on ng-include and on ng-controller will have a big impact matter
on ng-include it will not load the required partial and does not process unless flag is true
on ng-controller it will not load the controller unless flag is true
but the problem is when a flag gets false in ng-if it will remove from DOM when flag gets true back it will reload the DOM in this case ng-show is better, for one time show ng-if is better

Issue creating isolated scope in Angular + UI Bootstrap

I have a simple table of data, created using AngularJS. One of the columns of the table is calculated from a function on the controller.
I have a button on the page that opens a new modal. When I open a modal using UI bootstrap, I get a new isolated scope (child of the root scope), as expected. If, however, I have an input text in the modal any key-presses in this text field automatically invoke functions on the parent scope - even though I can verify that the scope is isolated.
Here is a plunkr of the behavior: http://plnkr.co/edit/JzhxSDcSefDe04Psxq0w
As shown in the example, the third column of the table is calculated with a function called "ageNextYear". When the table is being rendered, this function is called many times as expected (and can be verified in the console log). If however, I open the modal and type some text in to the field, the "ageNextYear" function on the parent scope still gets called (type some text in the input field and watch the console log output).
I'm not sure whether this is intended behavior, or whether I'm doing something wrong. I have tried using dot notation on both scopes, and explicitly passing a new scope to $modal.open, but with no joy.
I can get around the problem (by creating a watchCollection on "people" and updating the table that way - which may be a better way of doing this overall) but wanted to validate whether others have seen this behavior also.
The issue you are experiencing is not related to the scope of the Modal Dialog. The issue is related to the use of a function within an ng-repeat expression. In general, using functions within expressions is a performance issue, but it's a much larger problem within an ng-repeat. according to This excellent article regarding common pitfalls of using scopes,
When using expressions in views or watchers, you should always remember that an expression is called every time AngularJS thinks it is needed. You will not get the best performance using functions, you might even miss some change events.
That means an expression…
within a ng-repeat will be called for each item separately. Additionally, this is used by the repeat directive to determine data changes.
Can be evaluated multiple times in one digest. This can happen when you're using multiple directives or additional scope watchers.
Can be evaluated even if the direct scope seems to be unchanged.
Containing a function will not be evaluated if the return value of the function changes, but only if the function definition has changed.
Your example causes 3 of these 4 to occur.
You repeat the function call for each object in your scope, 3 items = 3 calls to the function.
You add an additional watcher indirectly by calling the Modal Dialog.
Changes to the data in the Modal Dialog's scope causes evaluation of the scope of the controller containing the ng-repeat, even though the data within the ng-repeat didn't change (no way for it to know if the data changed until the $digest is called). Each change to the Modal causes the $digest, which causes another trip through the ng-repeat, and another call for each item in the ng-repeat.
In your case, the logic does not need to run every time the expression will be evaluated. It is better to compute and write the logic into the scope when the logic result has changed. This decouples the logic from the object and the view.
in summary,
Best practices:
DO NOT use functions in expressions.
DO NOT use other data besides the scope in an expression.
DO use $scope.$apply() when applying external data changes.
Simon,
I liked your question and I added watch on the scope and saw the digest cycle is getting called
$scope.$watch(function watchMe(scope) { console.log('Digest watched me!'); });
The following is the fork with the digest.
http://plnkr.co/edit/5PTO1uPFvmLrg7K9vzTm?p=preview
I donot know this is the reason but I think expressions inside the ng-repeat are calling the digest as it tries to evaluate expression on any event on that item.
I think we should evaluate expressions in the model and give the updated model to the ng-repeat to solve the issue.

Categories

Resources