When using a guard to protect a route, or using a resolver to pre-fetch data, we can have a delay between the moment we navigate to a route, and the moment that our component is displayed. Notifying the user that something is happening in the background can greatly improve the User Experience.

The goal of this article is to show you how to display a loading indicator when navigating from one route to another.

The sample application

We will start with a sample application that looks like this:
before

And end up with an application that looks like this:
after

We want to display a progress bar at the start of every navigation, and hide it when the navigation is complete.

The Angular Router service gives us access to an Observable of all navigation events via its events property. We can subscribe to that Observable, and act accordingly depending on the type of the event.

If you want to follow along, you can grab the starter files from my Github repository

git clone https://github.com/ahasall/angular-routing-events-demo.git
cd angular-routing-events-demo
npm install

Listening to routing events

Let's listen to the route events in AppComponent. We want to display the loading indicator for every route of our application, therefore we must listen to navigation events in our root component. If we listen to navigation events from another component, we may lose some of the routing events.


import { Component } from '@angular/core';
import {
  Event,
  NavigationCancel,
  NavigationEnd,
  NavigationError,
  NavigationStart,
  Router
} from '@angular/router';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'Ng-Teams';
  loading = false;

  constructor(private router: Router) {
    this.router.events.subscribe((event: Event) => {
      switch (true) {
        case event instanceof NavigationStart: {
          this.loading = true;
          break;
        }

        case event instanceof NavigationEnd:
        case event instanceof NavigationCancel:
        case event instanceof NavigationError: {
          this.loading = false;
          break;
        }
        default: {
          break;
        }
      }
    });
  }
}
  • L18: We start by defining a boolean variable, loading, that determines whether we show the loading indicator or not.
  • L20: We inject the Router service into the root component's constructor.
  • L21: We subscribe to the router events Observable.
  • L23: If we are at the beginning of a navigation (NavigationStart), we set the loading indicator to true.
  • L28-L30: If we are at the end of a navigation (NavigationEnd), we reset the loading indicator to false. We also set the loading indicator to false if an error occurs ( NavigationError), or if the navigation is cancelled (NavigationCancel).

We want to display the loading indicator just under our application's main toolbar. Here is the HTML to achieve that.


<mat-toolbar color="primary">
  {{title}}
  <nav style="margin-left: auto">
    <a mat-button routerLink="/">Home</a>
    <a mat-button routerLink="/teams">Teams</a>
  </nav>
</mat-toolbar>
<mat-progress-bar
  *ngIf="loading" color="accent" mode="indeterminate">
</mat-progress-bar>
<router-outlet></router-outlet>
  • L8-10: If loading boolean is true, we display Angular Material's progress bar using our accent color.

This is all it takes to have a fully working loading indicator based on the router events. The complete code can be found in this Github repository.


If you enjoyed this article, follow @ahasall on Twitter for more content like this.