Sunday, 16 November 2014

Decide Between ngIf and ngShow / ngHide

When I first started using AngularJs I used ngShow and ngHide a lot and never even consider ngIf. When someone explained the power of the latter to me it completely changed the way I thought about programatically showing and hiding content with AngularJs. There are plenty of reasons why you would want to hide and show content, and it's worth considering which option is best for you each time you need to do so. A large part of your decision is going to be to do with whether or not you are using psuedo-classes to style the element and it's siblings.

ngIf

This attribute directive takes a boolean value, equation, or function that returns a boolean, as a value. When this value evaluates to true, then the element the attribute is on and all it's children elements are removed from the DOM.

HTML

Given this HTML as written:

<button ng-click="changClicked()"></button>
<p>This will always show</p>
<p ng-if="getClicked()">
    This will show if clicked is true.
</p>

<p>This will always show</p>


This is what is rendered in the browser when the value evaluates as true:

<button ng-click="changClicked()"></button>
<p>This will always show</p>
<!-- ngIf: getClicked() --><p ng-if="getClicked()" class="ng-scope">
    This will show if clicked is true.
</p><!-- end ngIf: getClicked() -->

<p>This will always show</p>


And when the value evaluates as false:

<button ng-click="changClicked()"></button>
<p>This will always show</p>
<!-- ngIf: getClicked() -->
<p>This will always show</p>


As you can see, AngularJs completely removes the element from the DOM, leaving behind only a placeholder.

When the element is brought back into the DOM it is returned in it's initial state, at the time of the page load. This means that any modifications you've made to this part of the DOM using jQuery or native JavaScript, etc., will be gone. This is definitely one of the times that you should never leave the AngularJs bubble whilst running.

CSS

One concern when using ngIf is that due to the elements being removed from and recreated in the DOM you need to be careful when using psuedo-classes. When an element is added or removed from the DOM the browser will recalculate the CSS and re-apply styles based on the new structure. This can be a really positive thing if you're design relies upon only certain elements having a particular style, such as margins in a grid layout, or alternate styling. Given this CSS:

p:nth-child(odd){
    background-color: deepskyblue;
}
p:nth-child(even){
    background-color: greenyellow;
}


An ngIf element and it's siblings would appear like this whilst it's visible:


And it's siblings would look like this whilst it isn't:


This is really desirable to me, and is nearly always the reason why I use ngIf over ngShow / ngHide.

JavaScript

When the element is injected back into the DOM it is recreated from it's initial state as upon removal it's scope is destroyed along with all the changes on that section of the DOM from outside the AngularJs bubble. Be careful at this point as you could lose any user input that you've received within this element before it was removed. When the element is recreated the scope is recreated using prototypal inheritance from the parent scope. If you try to attach your user input to the parent scope it will be lost upon removal of the element as well.

Another thing to consider is that by removing all of your AngularJs within this element when it's not visible to the user means that it isn't being touched during every digest cycle. This is a huge performance boost, which may not be noticeable on desktop but can be on mobile devices. I'm always amazed at how many times things get hit each digest cycle, so keeping as much out of the cycle as possible is always a really positive thing to do.

ngShow / ngHide

Both of these directives take a boolean value, equation, or function that returns a boolean, as a value. Both use the CSS display property to either show or hide the element based on whether the value equates to true. For ngShow, the element is visible when the value is true. For ngHide, the element is visible when the value is false.

HTML

Given this HTML as written:

<p>This will always show</p>
<p ng-show="getClicked()">
    This will show if clicked is true.
</p>

<p>This will always show</p>
<p ng-hide="getClicked()">
    This will show if clicked is false.
</p>

<p>This will always show</p>


This is what is rendered in the browser when the value of ngShow and ngHide evaluate as true:

<p>This will always show</p>
<p ng-show="getClicked()" class="">
    This will show if clicked is true.
</p>

<p>This will always show</p>
<p ng-hide="getClicked()" class="ng-hide">
    This will show if clicked is false.
</p>
<p>This will always show</p>


And when the value of ngShow and ngHide evaluate as false:

<p>This will always show</p>
<p ng-show="getClicked()" class="ng-hide">
    This will show if clicked is true.
</p>
<p>This will always show</p>
<p ng-hide="getClicked()" class="">
    This will show if clicked is false.
</p>

<p>This will always show</p>


As you can see, ngHide is added to the element when it's ngShow / ngHide value evaluates so that the element should not be visible. The styling for this is display: none !important;, which is built into AngularJs. You could also attach styles to this hook, but that important flag is going to be pretty hard to overrule.

You can also use ngAnimate which allows you to use ngEnter and ngLeave as styling hooks. I'll blog about that some other time.

CSS

As ngShow and ngHide use display to change the element's visibility any styling from psuedo-classes that rely on how many elements there are in the structure will continue to count the elements regardless of their visibility. This means that something such as alternate styling would break whilst an element is hidden using ngShow or ngHide. Given this styling:

p:nth-child(odd){
    background-color: deepskyblue;
}
p:nth-child(even){
    background-color: greenyellow;
}


The rendering of the DOM would appear like this when an element isn't showing:


This clearly isn't what is intended, and in this example the only problem is that it isn't very pretty. However, this affect in a grid layout would be disastrous as widget would be spaced in unintentional ways.

So, Which to Use?

The only time I would use ngShow and ngHide is if I was wanting to maintain some binding of user input. The downside is that you won't be able to use psuedo-classes for your styling of this element and it's siblings, and you will be taking a performance hit. These days I favour ngIf, and try to only use ngIf when showing and hiding content on the page.

No comments:

Post a Comment