Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: Guust Nieuwenhuis and Cyril Hanquez
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: Guust Nieuwenhuis@Lagaffe ) and Cyril Hanquez@Fitzchev )

Creating A Pre-Bootstrap Loading Screen In Angular 2 RC 1

By Ben Nadel on

Last year, I took a look at how to create a pre-bootstrap loading screen in an Angular 1.x application. In the comments for that post, Yaseen recently asked how one might accomplish the same thing in an Angular 2 application. I think the approach, in Angular 2, will basically be the same. But, since Angular 1.x and 2.x applications are fundamentally different in how they are bootstrapped, I thought it would be worth a quick look.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

In Angular 1.x, we generally included the entire HTML document in the "ng-app" directive. This meant that our pre-bootstrap loader would be automatically ingested as part of the Angular application. In Angular 2, however, things are slightly different; we no longer bootstrap the HTML document, we bootstrap a specific element on the page. As such, our pre-bootstrap content will no longer be subsumed by the core application.

In an Angular 2 application, I can think of two different approaches to hooking the pre-bootstrap content into the core application life-cycle. In the first approach, we can provide the pre-bootstrap content as the "child content" of the root component. And, in the second approach, we keep the pre-bootstrap content outside of the root component and programmatically hook into the System.js promise chain.

When we provide the pre-bootstrap content as part of the root component's "child content," we know that our pre-bootstrap view will automatically be destroyed at the end of the bootstrapping process. We know this because the "child content" will be replaced with the "template content" associated with the root component once the root component is loaded.

  • <!doctype html>
  • <html>
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Creating A Pre-Bootstrap Loading Screen In Angular 2 RC 1
  • </title>
  •  
  • <link rel="stylesheet" type="text/css" href="./demo.css"></link>
  •  
  • <!-- Load libraries (including polyfill(s) for older browsers. -->
  • <script type="text/javascript" src="../../vendor/angularjs-2-beta/rc1/node_modules/core-js/client/shim.min.js"></script>
  • <script type="text/javascript" src="../../vendor/angularjs-2-beta/rc1/node_modules/zone.js/dist/zone.js"></script>
  • <script type="text/javascript" src="../../vendor/angularjs-2-beta/rc1/node_modules/reflect-metadata/Reflect.js"></script>
  • <script type="text/javascript" src="../../vendor/angularjs-2-beta/rc1/node_modules/systemjs/dist/system.src.js"></script>
  • <!-- Configure SystemJS loader. -->
  • <script type="text/javascript" src="./system.config.js"></script>
  • </head>
  • <body>
  •  
  • <h1>
  • Creating A Pre-Bootstrap Loading Screen In Angular 2 RC 1
  • </h1>
  •  
  • <my-app>
  •  
  • <!--
  • Anything inside the <my-app> component content-area will be automatically
  • replaced with the "template" (associated with the <my-app> component) once
  • the application is bootstrapped. The upside of this is that it just happens
  • automatically. The downside of this is that the content instantaneously
  • disappears once the application has been bootstrapped.
  • -->
  •  
  • <style type="text/css">
  •  
  • #pre-bootstrap {
  • background-color: #262626 ;
  • bottom: 0px ;
  • left: 0px ;
  • position: fixed ;
  • right: 0px ;
  • top: 0px ;
  • z-index: 999999 ;
  • }
  •  
  • #pre-bootstrap div.messaging {
  • color: #FFFFFF ;
  • font-family: monospace ;
  • left: 0px ;
  • margin-top: -37px ;
  • position: absolute ;
  • right: 0px ;
  • text-align: center ;
  • top: 50% ;
  • }
  •  
  • #pre-bootstrap h1 {
  • font-size: 26px ;
  • line-height: 35px ;
  • margin: 0px 0px 20px 0px ;
  • }
  •  
  • #pre-bootstrap p {
  • font-size: 18px ;
  • line-height: 14px ;
  • margin: 0px 0px 0px 0px ;
  • }
  •  
  • </style>
  •  
  • <div id="pre-bootstrap">
  • <div class="messaging">
  •  
  • <h1>
  • App is Loading
  • </h1>
  •  
  • <p>
  • Please stand by for your ticket to awesome-town!
  • </p>
  •  
  • </div>
  • </div>
  •  
  • </my-app>
  •  
  • </body>
  • </html>

As you can see, all of our pre-bootstrap view logic is provided as the "child content" inside of the <my-app> root component. When root component is bootstrapped, this content will automatically be stripped out. This makes the integration extremely easy; but, it gives us little control over the user experience (UX) since the "child content" is instantaneously removed from the DOM (Document Object Model).

If we want to provide a slightly better user experience, we need to programmatically hook into the bootstrapping life-cycle. In order to do this, we need to take the Promise exposed by the System.js loader and make it available to the pre-bootstrap view. To do this, I'm going to store the result of the System.js .import() call into the global scope:

  • (function( global ) {
  •  
  • // .... code removed ....
  •  
  • System.config({
  • // .... code removed ....
  • });
  •  
  • // Load "./app/main.ts" (gets full path from package configuration above).
  • // --
  • // NOTE: We are attaching the resultant promise to the global scope so that other
  • // scripts may listen for the successful loading of the application.
  • global.bootstrapping = System
  • .import( "app" )
  • .then(
  • function handleResolve() {
  •  
  • console.info( "System.js successfully bootstrapped app." );
  •  
  • },
  • function handleReject( error ) {
  •  
  • console.warn( "System.js could not bootstrap the app." );
  • console.error( error );
  •  
  • return( Promise.reject( error ) );
  •  
  • }
  • )
  • ;
  •  
  • })( window );

As you can see, I'm taking the Promise of the application loader and storing it into "global.bootstrapping", which is really "window.bootstrapping" in this case due to the reference passed into the self-executing function. Once this promise is globally available, we can then hook into it in our pre-bootstrapping logic:

  • <!doctype html>
  • <html>
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Creating A Pre-Bootstrap Loading Screen In Angular 2 RC 1
  • </title>
  •  
  • <link rel="stylesheet" type="text/css" href="./demo.css"></link>
  •  
  • <!-- Load libraries (including polyfill(s) for older browsers. -->
  • <script type="text/javascript" src="../../vendor/angularjs-2-beta/rc1/node_modules/core-js/client/shim.min.js"></script>
  • <script type="text/javascript" src="../../vendor/angularjs-2-beta/rc1/node_modules/zone.js/dist/zone.js"></script>
  • <script type="text/javascript" src="../../vendor/angularjs-2-beta/rc1/node_modules/reflect-metadata/Reflect.js"></script>
  • <script type="text/javascript" src="../../vendor/angularjs-2-beta/rc1/node_modules/systemjs/dist/system.src.js"></script>
  • <!-- Configure SystemJS loader. -->
  • <script type="text/javascript" src="./system.config.js"></script>
  • </head>
  • <body>
  •  
  • <h1>
  • Creating A Pre-Bootstrap Loading Screen In Angular 2 RC 1
  • </h1>
  •  
  • <my-app>
  • Loading...
  • </my-app>
  •  
  •  
  • <div id="pre-bootstrap-container">
  •  
  • <!--
  • In this approach, rather than putting the pre-bootstrap content inside
  • the <my-app> component content, we're leaving it external to the Angular 2
  • application entirely. Then, we're hooking into the bootstrap promise in
  • order to programmatically remove the pre-bootstrap content once the
  • application has bootstrapped. This gives us more fine-grained control over
  • how the pre-bootstrap content is removed.
  • -->
  •  
  • <script type="text/javascript">
  •  
  • // CAUTION: "bootstrapping" promise exposed by our System.js logic.
  • window.bootstrapping.then(
  • function handleResolve() {
  •  
  • var preBootstrapContainer = document.getElementById( "pre-bootstrap-container" );
  • var preBootstrap = document.getElementById( "pre-bootstrap" );
  •  
  • // Add the class-name to initiate the transitions.
  • preBootstrap.className = "loaded";
  •  
  • // Remove the bootstrap container after the transition has
  • // completed (based on the known transition time).
  • setTimeout(
  • function removeLoadingScreen() {
  •  
  • preBootstrapContainer
  • .parentNode
  • .removeChild( preBootstrapContainer )
  • ;
  •  
  • },
  • 300
  • );
  •  
  • }
  • );
  •  
  • </script>
  •  
  • <style type="text/css">
  •  
  • #pre-bootstrap {
  • background-color: #262626 ;
  • bottom: 0px ;
  • left: 0px ;
  • opacity: 1.0 ;
  • position: fixed ;
  • right: 0px ;
  • top: 0px ;
  • transition: all linear 300ms ;
  • -webkit-transition: all linear 300ms ;
  • z-index: 999999 ;
  • }
  •  
  • #pre-bootstrap.loaded {
  • opacity: 0.0 ;
  • }
  •  
  • #pre-bootstrap div.messaging {
  • color: #FFFFFF ;
  • font-family: monospace ;
  • left: 0px ;
  • margin-top: -37px ;
  • position: absolute ;
  • right: 0px ;
  • text-align: center ;
  • top: 50% ;
  • }
  •  
  • #pre-bootstrap h1 {
  • font-size: 26px ;
  • line-height: 35px ;
  • margin: 0px 0px 20px 0px ;
  • }
  •  
  • #pre-bootstrap p {
  • font-size: 18px ;
  • line-height: 14px ;
  • margin: 0px 0px 0px 0px ;
  • }
  •  
  • </style>
  •  
  • <div id="pre-bootstrap">
  • <div class="messaging">
  •  
  • <h1>
  • App is Loading
  • </h1>
  •  
  • <p>
  • Please stand by for your ticket to awesome-town!
  • </p>
  •  
  • </div>
  • </div>
  •  
  • </div>
  •  
  • </body>
  • </html>

As you can see, I'm hooking into the promise resolution as a means to kick off an animated transition of the pre-bootstrap loading screen. By adding the "loaded" class, I'm forcing a change in the "opacity" property of the pre-bootstrap messaging. Then, I setup a timeout that will remove the pre-bootstrap from the DOM after the animation has completed. Using this approach requires a bit more logic; but, in the end, I think it provides a nicer user experience.


 
 
 

 
 Creating a pre-bootstrap loading screen in Angular 2 RC 1. 
 
 
 

As you can see, the pre-bootstrap loading screen is gracefully faded out to reveal the fully loaded application below.

Remember that a pre-bootstrap loading screen is intended to create a better user experience; but, at the same time, it's not intended to be resource heavy. This is why I am inlining all of the styles and excluding the use of any images as any additional HTTP requests may end-up delay the loading of the actual application. As the Angular 2 framework moves towards a fully-functional release, the approach to this problem might change; but, at this time, this is the best I can come up with.




Reader Comments

nice approach .. this will be very useful in case of hybrid apps that need to load heavy JS to bootstrap.

I have one question though. How would this be different if I want to listen to some other event that bootstrapping? (i.e. I want to trigger a flag or raise an event from my angular2 app, and have that event remove the pre-load content).

Reply to this Comment

@Moataz,

Ooooh, that is a terrific question! I'll have to think about that for a while. This has got to be possible. I'll get back to you on this on (hopefully with a follow-up demo).

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.