Angular Loading Screen for Module Navigation

Tom Cowley
4 min readFeb 20, 2017

--

Specifically, this is about trying to generate a loading screen for lazily loaded modules, as opposed to the initial loading screen for the whole app which can just be added in index.html.

tl:dr

  • Get a loading screen for lazily loaded modules:
  • app.component.ts
import { Component, AfterViewInit } from '@angular/core';
import {
Router, NavigationStart, NavigationCancel, NavigationEnd
} from '
@angular/router';
@Component({
selector: 'app-root',
template: `
<div class="app-layout">
<app-toolbar></app-toolbar>
<div [hidden]="!loading" class="loader">
<h2>Loading...</h2>
<md-progress-bar mode="indeterminate"></md-progress-bar>
</div>

<div [hidden]="loading" class="router-output">
<router-outlet></router-outlet>
</div>
</div>
`
styleUrls: ['./app.component.scss']
})
export class AppComponent implements AfterViewInit {
loading; constructor(
private router: Router
) {
this.loading = true;
}
ngAfterViewInit() {
this.router.events
.subscribe((event) => {
if(event instanceof NavigationStart) {
this.loading = true;
}
else if (
event instanceof NavigationEnd ||
event instanceof NavigationCancel
) {
this.loading = false;
}
});
}

}

The Problem

It may be easier just to show you what I mean:

The image above isn’t slowed down at all, it simply shows the time delay between clicking a navigation item, and the time until it loads. It’s also worth noting that there is extra hidden DOM content on those pages, to get a more real world loading time.

Although it is just a second or so, which is pretty standard for webpages when clicking between links, it seemed that we must be able to do better. Angular, and single page apps in general, are super responsive, but lazily loaded pages were feeling… sluggish. I found myself clicking on these links a couple of times, just because the UI wasn’t reacting immediately to a button press, whereas everywhere else in the app, it would be. And then on a slow network connection, the effect would be magnified.

The Result

Before I show you how, I just wanted to show the end result:

As soon as the link is clicked, a little loading screen is shown, indicating to the user, that things are happening.

Also worth noting, that with both of the above, once a lazily loaded module has been loaded, subsequent clicks result in an instant page load anyway. The loading screen is only shown the first time the modules get grabbed over the network. Subsequent clicks are instant, like this:

The Solution

Fortunately getting this solution required very little work, and was surprisingly simple.

app.component.html

The app.component.html page has a toolbar with the navigation buttons and a router outlet for the actual content:

<div class="app-layout">
<app-toolbar></app-toolbar>
<div class="router-output">
<router-outlet></router-outlet>
</div>
</div>

Then we need to add in a loading component, something like this:

<div class="app-layout">
<app-toolbar></app-toolbar>
<div class="loader">
<h2>Loading...</h2>
<md-progress-bar mode="indeterminate"></md-progress-bar>
</div>

<div class="router-output">
<router-outlet></router-outlet>
</div>
</div>

The progress bar is a simple component from angular material. Next, we need to be able to display and hide these based on a variable. I didn’t want to use *ngIf here, as I wanted to have the content ready to go in the DOM during loading. So instead, I’ve bound the hidden property:

<div class="app-layout">
<app-toolbar></app-toolbar>
<div [hidden]="!loading" class="loader">
<h2>Loading...</h2>
<md-progress-bar mode="indeterminate"></md-progress-bar>
</div>
<div [hidden]="loading" class="router-output">
<router-outlet></router-outlet>
</div>
</div>

app.component.ts

Now, the hard part. How do we…

a) Know when the button has been clicked loading = true
b) Know when the module has finished loading loading = false

Let’s begin with the following:

import { Component} from '@angular/core';@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
loading;constructor(
) {
this.loading = true;
}
}

On the Angular Router, we actually get given an event that we can subscribe to. The events that we have available are:

  • NavigationStart
  • NavigationEnd
  • NavigationCancel
  • NavigationError
  • RoutesRecognized

For now, we’re concerned with just the first three. If we grab our relevant imports and put it all together, we end up with:

import { Component, AfterViewInit } from '@angular/core';
import {
Router, NavigationStart, NavigationCancel, NavigationEnd
} from '
@angular/router';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements AfterViewInit {
loading; constructor(
private router: Router
) {
this.loading = true;
}
ngAfterViewInit() {
this.router.events
.subscribe((event) => {
if(event instanceof NavigationStart) {
this.loading = true;
}
else if (
event instanceof NavigationEnd ||
event instanceof NavigationCancel
) {
this.loading = false;
}
});
}

}

And that’s it! The app now feels much more responsive when loading the lazy modules, with just a few lines of code.

--

--

Tom Cowley

Once a maths teachers. Now a Web Developer. Maybe one day, a blogger?