Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at Hal Helms' Real World OO (Feb. 2009) with: Steve Good and Joakim Marner and Charlie Griefer and Ezra Parker
Ben Nadel at Hal Helms' Real World OO (Feb. 2009) with: Steve Good@stevegood ) , Joakim Marner , Charlie Griefer@charliegriefer ) , and Ezra Parker

Revisited: Creating An Event-Driven Pre-Bootstrap Loading Screen In Angular 2.0.0

By Ben Nadel on

Earlier this week, I revisited the idea of creating a pre-bootstrap loading screen in Angular 2 (which was, itself, a revisiting of an earlier post). In that post, I used the Document Object Model (DOM) as a means to cross the application boundary, allowing the app to announce an "appready" event to the host page, which was managing the pre-bootstrap loading screen. While I like this idea, I didn't really like my particular implementation as it allowed the concept of the DOM to leak into the greater context. Today, I wanted to briefly revisit this idea (again), simply to refactor it into a set of boundaries that feel cleaner and more platform-agnostic.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

In my previous implementation, the carrier of the "appready" event was a service called DOMEvents. The root component then explicitly triggered the "appready" event on the DOM using using the following method:

domEvents.triggerOnDocument( "appready" );

The problem with this is that it clearly ties the entire mechanism to the DOM, which in turn, clearly ties it to the browser. This means that if we ever implemented this app on a different platform that didn't necessarily have a Document or a similar "DOM" concept, we'd end up with a service implementation whose name was terribly misleading.

To fix this, I refactored the service to be called "AppReadyEvent" which only exposes a single method (at this time): .trigger():

  • // Import the core angular services.
  • import { DOCUMENT } from "@angular/platform-browser";
  • import { Inject } from "@angular/core";
  • import { Injectable } from "@angular/core";
  •  
  • @Injectable()
  • export class AppReadyEvent {
  •  
  • private doc: Document;
  • private isAppReady: boolean;
  •  
  •  
  • // I initialize the service.
  • // --
  • // NOTE: When I first tried to approach this problem, I was going to try and use the
  • // core Renderer service; however, it appears that the Renderer cannot be injected
  • // into a service object (throws error: No provider for Renderer!). As such, I am
  • // treating THIS class as the implementation of the DOM abstraction (so to speak),
  • // which can be overridden on a per-environment basis.
  • constructor( @Inject( DOCUMENT ) doc: any ) {
  •  
  • this.doc = doc;
  • this.isAppReady = false;
  •  
  • }
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I trigger the "appready" event.
  • // --
  • // NOTE: In this particular implementation of this service on this PLATFORM, this
  • // simply triggers the event on the DOM (Document Object Model); however, one could
  • // easily imagine this event being triggered on an Observable or some other type of
  • // message transport that makes more sense for a different platform. Nothing about
  • // the DOM-interaction leaks outside of this service.
  • public trigger() : void {
  •  
  • // If the app-ready event has already been triggered, just ignore any subsequent
  • // calls to trigger it again.
  • if ( this.isAppReady ) {
  •  
  • return;
  •  
  • }
  •  
  • var bubbles = true;
  • var cancelable = false;
  •  
  • this.doc.dispatchEvent( this.createEvent( "appready", bubbles, cancelable ) );
  • this.isAppReady = true;
  •  
  • }
  •  
  •  
  • // ---
  • // PRIVATE METHODS.
  • // ---
  •  
  •  
  • // I create and return a custom event with the given configuration.
  • private createEvent(
  • eventType: string,
  • bubbles: boolean,
  • cancelable: boolean
  • ) : Event {
  •  
  • // IE (shakes fist) uses some other kind of event initialization. As such,
  • // we'll default to trying the "normal" event generation and then fallback to
  • // using the IE version.
  • try {
  •  
  • var customEvent: any = new CustomEvent(
  • eventType,
  • {
  • bubbles: bubbles,
  • cancelable: cancelable
  • }
  • );
  •  
  • } catch ( error ) {
  •  
  • var customEvent: any = this.doc.createEvent( "CustomEvent" );
  •  
  • customEvent.initCustomEvent( eventType, bubbles, cancelable );
  •  
  • }
  •  
  • return( customEvent );
  •  
  • }
  •  
  • }

As you can see, the implementation on the Browser platform is still tied to the Document Object Model. However, at this point, there's nothing in the naming of the service or its methods that indicate a platform relationship, creating a much cleaner abstraction.

And, with a cleaner abstraction at the event-service level, our root component also becomes much cleaner:

  • // Import the core angular services.
  • import { Component } from "@angular/core";
  •  
  • // Import the application services.
  • import { AccountService } from "./account.service";
  • import { AppReadyEvent } from "./app-ready-event";
  • import { IAccount } from "./account.service";
  •  
  • @Component({
  • selector: "my-app",
  • template:
  • `
  • <template [ngIf]="account">
  •  
  • <h3>
  • Welcome {{ account.name }}.
  • </h3>
  •  
  • <p>
  • I hope youre having a beautiful day!
  • </p>
  •  
  • </template>
  • `
  • })
  • export class AppComponent {
  •  
  • public account: IAccount;
  •  
  •  
  • // I initialize the component.
  • constructor( accountService: AccountService, appReadyEvent: AppReadyEvent ) {
  •  
  • this.account = null;
  •  
  • // At this point, the application has "loaded" in so much as the assets have
  • // loaded; but, the we're not going to consider the application "ready" until
  • // the core "data" has loaded. As such, we won't trigger the "appready" event
  • // until the account has been loaded.
  • accountService.getAccount().subscribe(
  • ( account ) => {
  •  
  • this.account = account;
  •  
  • // Now that the core data has loaded, let's trigger the event that the
  • // pre-bootstrap loading screen is listening for. This will initiate
  • // the teardown of the loading screen.
  • appReadyEvent.trigger();
  •  
  • }
  • );
  •  
  • }
  •  
  • }

Now, when the root component has its data loaded, it just triggers the AppReadyEvent. It doesn't know anything about the DOM - it doesn't know that this event is actually being triggered on the "document". It only knows that there's a service it should call.

I won't bother showing the rest of the code as it can all be found in the previous post. I just wanted to revisit this because my prior naming choices and lines of encapsulation where gnawing at my brain. As I struggle to wrap my head around platform-agnostic thinking, I'm sure I will continue to stumble a lot; but exercises like this are helping.



Looking For A New Job?

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

Hi,
I love your posts.

I am trying to create a loading progress bar that progresses as elements are loaded and reaches 100% when everything is fetched from the server.

I am new to angular and I can't seem to understand how can I subscribe to loading events for all the resources or anythings similar.

Thanks for the great work you are doing :)

Reply to this Comment

@Sam,

Oh snap! I had no idea that was needed. Which is kind of funny because I use MDN all the time when I have to look up custom Event creation and I don't remember seeing anything about IE, other than that it used a different method workflow (which I tried to use in my post). I didn't realize that this different workflow itself needed to by polyfilled. Thanks for the insight!

Reply to this Comment

@Razvan,

That is a fantastic concept and is one that I have tried to think about a bit in the past. In my current demos, I have to load over 200+ files using System.js and it would be great to show some indication to the user that this is happening.

BUT, I temper that with the understanding that in a production system, I wouldn't be using System.js to load hundreds of files. Instead, I would load some "distribution" file that was pre-compiled down into one (or a few) massive JavaScript files. So, in that case, a progress bar makes less sense.

That said, I keep meaning to look at System.js to see if it emits any event as it loads individual files. Then, at least in my demos, I could tap into that event and put something on the screen.

So, to your question, I don't have a good answer - but, it is something I have been thinking about.

Reply to this Comment

Hey Ben,

I like ur vids/posts about the pre-bootstrap loading screens! I'm searching for the same solution as Razvan (loading indicator which shows how far the app is loaded). The last post to that is some weeks ago, so maybe u have an approach right now to get the solution?

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.