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

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

Related

Race conditions on directives

I have a directive that calculates the height and distance from the top of the window of an element that is passed in as an id to an attribute. The issue I'm running into is that I have several of these directives running (on elements that are attached to the same directive), and I'm running into a race condition where some of the directives that are lower down in the DOM are running before the ones that are higher up for whatever reason.
Is there a way I can make the bottom ones wait for the ones higher up via a promise or something? If so, how would I implement that? Is there a better way of handling this?
If you want code to be executed by your directive in the descending order (higer in the DOM comes first), you should either put that code in your directive's controller, or preLink methods.
The default link method is a shortcut to postLink which is executed in a ascending order.
That being said, I don't know your code so I am just making a guess, but it sounds like a service would be more appropriate than a directive, unless the calcul you're doing is done directly in that directive element, in which case using $element should be enough. But you should probably not pass an id or selector to a directive.

AngularJS ng-if directive briefly renders even when condition is false before removing element

In the below template, I would expect the script tag to never render, and the alert script to never execute. However it does.
<div ng-if="false">
<script>alert('should not run')</script>
Should not appear
</div>
This is causing us huge performance problems on mobile devices as we wrap large DOM and directive structures in ng-ifs with the expectation they will not render when the condition is false.
I have also tested ng-switch which behaves in the same manner.
Is this expected behaviour? Is there a way to avoid the unnecessary render?
JSFiddle
It may seem backward, but ngIf deals more with the removal of DOM, rather than the addition. Before the controller finishes instantiating, the DOM still exists. This is generally a good thing, and allows you to have graceful degradation for users without JS (or, alternatively, an initial loading state).
If you don't want the inner DOM to render, place it in a directive (either its own directive, or via ng-include) or in a view.
Example 1 (understanding why the script runs):
To help yourself understand the flow a bit better, you can update the example to instead be:
<div ng-if="false">
{{"Should not appear"}}
<script>alert('should not run')</script>
</div>
https://jsfiddle.net/hLw0nady/6/embedded/result/
You will notice that when the alert pops up, Angular has not yet interpolated "Should not appear" (it appears in its braces). After you dismiss the alert, however, it disappears.
Example 2 (how to prevent the alert):
An example of hiding the code that "should not run" in a directive can be viewed here: https://jsfiddle.net/hLw0nady/4/
In this example, only if you replace ng-if="false" with ng-if="true" will you get your alert.
I think that false expression is not well converted to angular false.
I can prove this by setting:
<div ng-if="!true">
Which doesn't render text in current div.
Anyway, it executes alert, i suppose it is executed before angular runs, that's why.

Angularjs performance when binding to deeply nested object attributes

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

Is there a good way to 'Freeze' Angular scopes and children that are not being used

I am working on a project that basically consists of complicated form containers repeated in an ng-repeat. Each of these forms has enough functionality that it could easily be its own angular app, but instead they are repeated on the screen, and there can be up to 30 of them.
With 5 or more of these on the screen, performance becomes very slow. Although I tried to disbale some of our heavier features, it seems that the big performance wins just come from disabling the 'uninteractable' scopes.
I am looking for a good pattern to 'freeze' the unused scopes. I want them to maintain their state, but I don't want them to listen or be attached to anything until the user focuses on the form that they apply to.
Without seeing your code my first thought is to use a directive to control that.
You could use the bind to the forms, and or use $watch(using $watch would depend on your setup) so that you have finer control over the scope.
here are some links:
for directives in general
for $watch and other ways of controlling the scope
Are all the forms in the view port at the same time? If not, you could add / remove forms to the DOM (using ng-include f.e.) based on whether they are visible to the user. This way watches are removed and added again when needed.
Otherwise, if you have a lot of watches you specify in your form controller, you can unwatch when not focused and rewatch when getting back the focus. This won't reduce the number of watches created by the (native) directives inside your form. This number can perhaps be reduced by using bind-once.

Angular.js change on one item of ng-repeat causing filters on all other items to run

I'm still running into the same problem, filters and functions inside ng-repeat being called all the damn time.
Example here, http://plnkr.co/edit/G8INkfGZxMgTvPAftJ91?p=preview, anytime you change something on a single row, someFilter filter is called 1000 times.
Apparently it's because any change on a child scope bubbles up to its parent, causing $digest to run, causing all filters to run(https://stackoverflow.com/a/15936362/301596). Is that right? How can I prevent it from happening in my particular case?
How can I make it run only on the item that has changed?
In my actual use case the filter is called even when the change is not even on the items of ng-repeat, it's so pointless and it is actually causing performance problems..
// edit cleared all the unnecessary stuff from the plunker
http://plnkr.co/edit/G8INkfGZxMgTvPAftJ91?p=preview
This is just how Angular's dirty checking works. If you have an array of 500 items and the array changes, the filter must be reapplied to the entire array. And now you're wondering "why twice"?
From another answer:
This is normal, angularjs uses a 'dirty-check' approach, so it need to call all the filters to see if exists any change. After this it detect that have a change on one variable(the one that you typed) and then it execute all filters again to detect if has other changes.
And the answer it references: How does data binding work in AngularJS?
Edit: If you're really noticing sluggishness (which I'm not on an older Core 2 Duo PC), there are probably a number of creative ways you can get around it depending on what your UI is going to be.
You could put the row into edit mode while the user is editing the data to isolate the changes, and sync the model back up when the user gets out of edit mode
You could only update the model onblur instead of onkeypress using a directive, like this: http://jsfiddle.net/langdonx/djtQR/1/

Categories

Resources