Preload AngularJS Templates
Avoid unnecessary/redundant network requests
This post you're reading is a static page generated by Metalsmith and hosted on AWS S3 serverlessly. However, even though this site is static and serverless, I still wanted it to feel dynamic. I thought one way I might accomplish this was to find a way to handle all of the front end routing with Angular, UI-Router and HTML5 mode.
In HTML5 mode, the $location service getters and setters interact with the browser URL address through the HTML5 history API. This allows for use of regular URL path and search segments, instead of their hashbang equivalents. If the HTML5 History API is not supported by a browser, the $location service will fall back to using the hashbang URLs automatically. This frees you from having to worry about whether the browser displaying your app supports the history API or not; the $location service transparently uses the best available option.
In this way, navigating from page to page doesn't trigger a full page reload and it makes the site feel more "application" like.
One way to accomplish this is to have all of your content stored in "partials". e.g. serve your main Angular layout for every request and then subsequently load the desired partial/content for that route. For example, your layout might look something like this:
<!-- Result for http://domain.tld/blog --> <html ng-app> <body> <header> <!-- Header content --> </header> <div id="main" ui-view> </div> <footer> <!-- Footer content --> </footer> </body> </html>
And a partial, like this:
<!-- Blog partial for page http://domain.tld/blog located at http://domain.tld/partials/blog --> <div class="blog-partial"> <h1>Welcome to my blog</h1> <p>Here is my content!</p> </div>
But as a consequence of this approach, our initial page load requires two requests to construct a complete page. Ew. We can do better than that. The whole point of using UI-Router was to make things feel snappy and dynamic. Luckily, Angular has a way to inline templates directly into the DOM with $templateCache. So for the initial request we'll just pack the partial in with the layout so the second request for the partial isn't needed.
Compacted, our HTML looks something like this now:
<!-- Result for http://domain.tld/blog --> <html ng-app> <body> <header> <!-- Header content --> </header> <div id="main" ui-view> </div> <footer> <!-- Footer content --> </footer> <!-- "id" of the template is equal to where we would normally load the partial --> <script type="text/ng-template" id="/partials/blog"> <div class="blog-partial"> <h1>Welcome to my blog</h1> <p>Here is my content!</p> </div> </script> </body> </html>
**Side Note:** Compacting our initial page load and serving additional pages via partials means we have to generate our content in two forms. Given that this is a static site, this creates a new challenge which I address in this blog.
Cool so now every thing is perfect right? Wrong. When we run it through Google Page Speed this is what it says:
Your page requires additional network round trips to render the above-the-fold content. For best performance, reduce the amount of HTML needed to render above-the-fold content.
The entire HTML response was not sufficient to render the above-the-fold content. This usually indicates that additional resources, loaded after HTML parsing, were required to render above-the-fold content. Prioritize visible content that is needed for rendering above-the-fold by including it directly in the HTML response.
So what's going on? I thought we eliminated that additional roundtrip... We did, however the initial render of the page is still missing our content. We still have to wait for Angular to bootstrap and UI-Router to do it's magic to resolve the correct template and update the DOM. Our content will eventually jump into existence but it takes time. However, we already know what our content is and where it needs to go, so we're going to beat UI-Router to the punch and add the following snippet of code to our layout right below the template:
<script> var partialContent = document.getElementById('/partials/blog').innerHTML; document.getElementById('main').innerHTML = partialContent; </script>
This code will execute even before the browser has fired the ready event, it's safe for us to manipulate the DOM here because the portions we're modifying are above this snippet. This will inject our template right to where it needs, Google Page Speed and most importantly our site visitors will never be the wiser.