Skip to main content
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Brad Wood
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Brad Wood

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

By
Published in Comments (12)

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.

Want to use code from this post? Check out the license.

Reader Comments

2 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).

15,848 Comments

@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).

15,848 Comments

@Frank,

Very interesting and clever use of sibling CSS selectors. While I love CSS, some of the more advanced stuff like that is not as accessible to me. I think I've only used the sibling CSS selectors once -- and that was because I was editing someone else' code that was already using it :D

Thanks for sharing though, this is pretty neat. What's especially cool is that you even get the CSS transitions to work when the :empty is no longer true.

This pleases me.

2 Comments

Hey Ben,

Thank you for the good tutorial. I choose to use the CSS approach. But do you know if it possible to put a gif on this page?

Because If I put an image is it perfect but the gif isn't animated. Maybe a trick exists for this situation?

Thanks

1 Comments

Hi Ben,

Thanks for the nice tutorial. However, I am looking for the same approach to be implemented in Angular CLI which is using webpack. I will be grateful if you can point me to other resources and any other hints are welcome. Thanks again.

Kj

3 Comments

Hi Ben,

An excellent tutorial. Thank you.

I'm using Angular CLI so there isn't a system.config.js.

Do you know have any insight into how I might go about doing this?

Many thanks,
Luke

15,848 Comments

@Luke, @Kj,

Thanks, glad you're enjoying. I actually have a more recent version of this exploration that doesn't use the System.js stuff at all:

www.bennadel.com/blog/3151-revisited-creating-an-event-driven-pre-bootstrap-loading-screen-in-angular-2-0-0.htm

... this newer version create an "AppReadyEvent" service that can be triggered from within the application. This way, you rely on the normal life-cycle of the application to tell the calling context when it is done. I much prefer that since it is technology independent.

15,848 Comments

@Mickael,

That's weird about the GIF. I can't think of a reason that it wouldn't animate. Not sure what to tell you.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel