Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

AngularJs ng-cloak Problems on large Pages


:P
On this page:

cloakedI’ve been working on a rather complex and large Angular page. Unlike a typical AngularJs SPA style ‘application’ this particular page is just that: a single page with a large amount of data on it that has to be visible all at once. The problem is that when this large page loads it flickers and displays template markup briefly before kicking into its actual content rendering. This is is what the Angular ng-cloak is supposed to address, but in this case I had no luck getting it to work properly.

This application is a shop floor app where workers need to see all related information in one big screen view, so some of the benefits of Angular’s routing and view swapping features couldn’t be applied. Instead, we decided to have one very big view but lots of ng-controllers and directives to break out the logic for code separation. For code separation this works great – there are a number of small controllers that deal with their own individual and isolated application concerns.

For HTML separation we used partial ASP.NET MVC Razor Views which made breaking out the HTML into manageable pieces super easy and made migration of this page from a previous server side Razor page much easier. We were also able to leverage most of our server side localization without a lot of  changes as a bonus. But as a result of this choice the initial HTML document that loads is rather large – even without any data loaded into it, resulting in a fairly large DOM tree that Angular must manage.

Large Page and Angular Startup

The problem on this particular page is that there’s quite a bit of markup – 35k’s worth of markup without any data loaded, in fact. It’s a large HTML page with a complex DOM tree. There are quite a lot of Angular {{ }} markup expressions in the document.

Angular provides the ng-cloak directive to try and hide the element it cloaks so that you don’t see the flash of these markup expressions when the page initially loads before Angular has a chance to render the data into the markup expressions.

<div id="mainContainer" class="mainContainer boxshadow"
     ng-app="app" ng-cloak>

Note the ng-cloak attribute on this element, which here is an outer wrapper element of the most of this large page’s content. ng-cloak is supposed to prevent displaying the content below it, until Angular has taken control and is ready to render the data into the templates.

Alas, with this large page the end result unfortunately is a brief flicker of un-rendered markup which looks like this:

Unrendered

It’s brief, but plenty ugly – right?  And depending on the speed of the machine this flash gets more noticeable with slow machines that take longer to process the initial HTML DOM.

ng-cloak Styles

ng-cloak works by temporarily hiding the marked up element and it does this by essentially applying a style that does this:

[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
  display: none !important;
}

This style is inlined as part of AngularJs itself. If you looking at the angular.js source file you’ll find this at the very end of the file:

!angular.$$csp() && angular.element(document)
.find(
'head')
.prepend(
'<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],' +
'
[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,' +
'.ng-hide{display:none !important;}ng\\:form{display:block;}'
'.ng-animate-block-transitions{transition:0s all!important;-webkit-transition:0s all!important;}' +
'</style>'
);

This is is meant to initially hide any elements that contain the ng-cloak attribute or one of the other Angular directive permutation markup. Unfortunately on this particular web page ng-cloak had no effect – I still see the flicker.

Why doesn’t ng-cloak work?

The problem is of course – timing. The problem is that Angular actually needs to get control of the page before it ever starts doing anything like process even the ng-cloak attribute (or style etc). Because this page is rather large (about 35k of non-data HTML) it takes a while for the DOM to actually plow through the HTML. With the Angular <script> tag defined at the bottom of the page after the HTML DOM content there’s a slight delay which causes the flicker.

For smaller pages the initial DOM load/parse cycle is so fast that the markup never shows, but with larger content pages it may show and become an annoying problem.

Workarounds

There a number of simple ways around this issue and some of them are hinted on in the Angular documentation.

Load Angular Sooner

One obvious thing that would help with this is to load Angular at the top of the page  BEFORE the DOM loads and that would give it much earlier control. The old ng-cloak documentation actually recommended putting the Angular.js script into the header of the page (apparently this was recently removed), but generally it’s not a good practice to load scripts in the header for page load performance. This is especially true if you load other libraries like jQuery which should be loaded prior to loading Angular so it can use jQuery rather than its own jqLite subset. This is not something I normally would like to do and also something that I’d likely forget in the future and end up right back here :-).

Use ng-include for Child Content

Angular supports nesting of child templates via the ng-include directive which essentially delay loads HTML content. This helps by removing a lot of the template content out of the main page and so getting control to Angular a lot sooner in order to hide the markup template content.

In the application in question, I realize that in hindsight it might have been smarter to break this page out with client side ng-include directives instead of MVC Razor partial views we used to break up the page sections. Razor partial views give that nice separation as well, but in the end Razor puts humpty dumpty (ie. the HTML) back together into a whole single and rather large HTML document. Razor provides the logical separation, but still results in a large physical result document.

But Razor also ended up being helpful to have a few security related blocks handled via server side template logic that simply excludes certain parts of the UI the user is not allowed to see – something that you can’t really do with client side exclusion like ng-hide/ng-show – client side content is always there whereas on the server side you can simply not send it to the client.

Another reason I’m not a huge fan of ng-include is that it adds another HTTP hit to a request as templates are loaded from the server dynamically as needed. Given that this page was already heavy with resources adding another 10 separate ng-include directives wouldn’t be beneficial :-)

ng-include is a valid option if you start from scratch and partition your logic. Of course if you don’t have complex pages, having completely separate views that are swapped in as they are accessed are even better, but we didn’t have this option due to the information having to be on screen all at once.

Avoid using {{ }}  Expressions

The biggest issue that ng-cloak attempts to address isn’t so much displaying the original content – it’s displaying empty {{ }} markup expression tags that get embedded into content. It gives you the dreaded “now you see it, now you don’t” effect where you sometimes see three separate rendering states: Markup junk, empty views, then views filled with data.

If we can remove {{ }} expressions from the page you remove most of the perceived double draw effect as you would effectively start with a blank form and go straight to a filled form. To do this you can forego {{ }}  expressions and replace them with ng-bind directives on DOM elements.

For example you can turn:

<div class="list-item-name listViewOrderNo">
    <a href='#'>{{lineItem.MpsOrderNo}}</a>
</div>
into:
<div class="list-item-name listViewOrderNo">
    <a href="#" ng-bind="lineItem.MpsOrderNo"></a>
</div>

to get identical results but because the {{ }}  expression has been removed there’s no double draw effect for this element.

Again, not a great solution. The {{ }} syntax sure reads cleaner and is more fluent to type IMHO. In some cases you may also not have an outer element to attach ng-bind to which then requires you to artificially inject DOM elements into the page. This is especially painful if you have several consecutive values like {{Firstname}} {{Lastname}} for example. It’s an option though especially if you think of this issue up front and you don’t have a ton of expressions to deal with.

Add the ng-cloak Styles manually

You can also explicitly define the .css styles that Angular injects via code manually in your application’s style sheet. By doing so the styles become immediately available and so are applied right when the page loads – no flicker.

I use the minimal:

[ng-cloak]
{
  display: none !important;
}

which works for:

<div id="mainContainer" class="mainContainer dialog boxshadow"
     ng-app="app" ng-cloak>

If you use one of the other combinations add the other CSS selectors as well or use the full style shown earlier. Angular will still load its version of the ng-cloak styling but it overrides those settings later, but this will do the trick of hiding the content before that CSS is injected into the page.

Adding the CSS in your own style sheet works well, and is IMHO by far the best option.

The nuclear option: Hiding the Content manually

Using the explicit CSS is the best choice, so the following shouldn’t ever be necessary. But I’ll mention it here as it gives some insight how you can hide/show content manually on load for other frameworks or in your own markup based templates.

Before I figured out that I could explicitly embed the CSS style into the page, I had tried to figure out why ng-cloak wasn’t doing its job. After wasting an hour getting nowhere I finally decided to just manually hide and show the container. The idea is simple – initially hide the container, then show it once Angular has done its initial processing and removal of the template markup from the page.

You can manually hide the content and make it visible after Angular has gotten control. To do this I used:

<div id="mainContainer" class="mainContainer boxshadow"
     ng-app="app" style="display:none">

Notice the display: none style that explicitly hides the element initially on the page.

Then once Angular has run its initialization and effectively processed the template markup on the page you can show the content. For Angular this ‘ready’ event is the app.run() function:

app.run( function ($rootScope, $location, cellService) { $("#mainContainer").show();

});

This effectively removes the display:none style and the content displays. By the time app.run() fires the DOM is ready to displayed with filled data or at least empty data – Angular has gotten control.

Edge Case

Clearly this is an edge case. In general the initial HTML pages tend to be reasonably sized and the load time for the HTML and Angular are fast enough that there’s no flicker between the rendering times. This only becomes an issue as the initial pages get rather large.

Regardless – if you have an Angular application it’s probably a good idea to add the CSS style into your application’s CSS (or a common shared one) just to make sure that content is always hidden. You never know how slow of a browser somebody might be running and while your super fast dev machine might not show any flicker, grandma’s old XP box very well might…

Posted in Angular  JavaScript  CSS  HTML  

The Voices of Reason


 

Maurice
June 03, 2014

# re: AngularJs ng-cloak Problems on large Pages

I agree manually adding the ng-cloak style is the easiest way.

I often use ng-include to avoid this. You can prevent the additional HTTP request by preloading the template cache. Do that by including script elements with type="text/ng-template" and the id matching the URL or the template. Something like:

<script type="text/ng-template" id="/Templates/Books/List">
@Html.Action("List")
</script>

BTW Avoiding {{}} doesn't really work. The same FOUC appears with an ng-repeat.

Zote
June 03, 2014

# re: AngularJs ng-cloak Problems on large Pages

try

<div class='ng-cloak'></div>

instead of

<div ng-cloak></div>

Regards

Rick Strahl
June 03, 2014

# re: AngularJs ng-cloak Problems on large Pages

@Zote - doesn't matter whether you use ng-cloak or class="ng-cloak". The CSS will capture both and try to hide it *if it is there*. With large files though it's important to add the CSS manually rather than let Angular inject it.

John Papa
June 05, 2014

# re: AngularJs ng-cloak Problems on large Pages

Rick, I use the manual CSS for this. My CSS always loads first in my apps, thus having a style for ng-cloak covers this situation nicely since it runs first before any rendering or angular code.

thanhtruongngoc
August 13, 2014

# re: AngularJs ng-cloak Problems on large Pages

hi,

It works well with style.

thanks,

Alex
December 01, 2014

# re: AngularJs ng-cloak Problems on large Pages

style="display:none"
doesn't work for modern versions of AngularJs

Rick Strahl
December 02, 2014

# re: AngularJs ng-cloak Problems on large Pages

@Alex - works for me with Angular 1.3.3. I don't see why it wouldn't work - it's just CSS you're setting.

Torgeir
December 10, 2014

# re: AngularJs ng-cloak Problems on large Pages

Sounds like you have your solution, but I how about this approach?

I have found that for large DOMs it often makes sense to use ng-if to toggle content. The ng-if directive appears similar to ng-hide/show, but with the important difference that it physically removes the actual DOM elements rather than hiding them using styles. My experience is that you can trim your DOM a lot this way. Also, another added benefit is that you don't incur watches on the ng-if excluded content like you would with the ng-show approach.

I have some more info in an article I wrote a while back:
http://www.syntaxsuccess.com/#/viewarticle/547a8ba2c26c307c614c715e

You make a good point by saying that it's easier to control permission based content by returning it from the server. However, I would argue that the same could be accomplished by having your angular application talk to a data api with server side permission checks instead. One idea would be to move away from an mvc, view based application, and instead talk to a web api service that returns json based on user permissions in tandem with ng-if.

Anyway, not sure if this is useful, but just a thought.

Johan
August 26, 2015

# re: AngularJs ng-cloak Problems on large Pages

I've had problems with the brief displayof angularJS expressions too, even though i was using the ng-cloak construction. (including the ng-cloak styleclass with display: none !important; in my css file) It turned out that in my case, the ng-cloak construction was working properly, but the flickering was caused by debugging tools. In my case firebug was also enabled, which caused slow rendering of the page (timing issues). When I disabled firebug for my page, everything worked as it was supposed to. (no flickering)

Luis Palacios
March 23, 2016

# re: AngularJs ng-cloak Problems on large Pages

The nuclear option actually help me a lot dealing with elements in a modal, thank you!

Michael
June 26, 2016

# re: AngularJs ng-cloak Problems on large Pages

Thank you Rick, adding the ng-cloak style manually resolved my issue and I am using angular 1.5.6

Suyog Talekar
April 22, 2019

# re: AngularJs ng-cloak Problems on large Pages

For me, overflow was creating problem, There was one extra overflow coming up while scrolling down page and DOM was not able to identify which scroll to use. I also changed from ng-show to ng-if to resolve this flickering issue


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024