Write Angular like a pro. Angular Icon

Follow the ultimate Angular roadmap.

Angular Loading Spinners with Router Events

In this post you’ll learn how to create a loading spinner that will show and hide based on your application’s loading state. To achieve this, we’ll be using the Angular Router and hooking into some of the events provided.

A loading spinner is typically best displayed in the root component, which is usually our AppComponent.

First off let’s start with an empty component, import and inject our Router. This will then enable us to subscribe to the Router events:

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-root',
  template: `
    <div class="app">
      <h1>My App</h1>
      <router-outlet></router-outlet>
    </div>
  `,
})
export class AppComponent implements OnInit {
  constructor(private router: Router) {}

  ngOnInit() {}
}

Inside our OnInit lifecycle hook we’ll setup a subscription to the events property on the Router:

@Component({...})
export class AppComponent implements OnInit {
  constructor(private router: Router) {}

  ngOnInit() {
    this.router.events.subscribe(console.log);
  }
}

In our developers tool console we’ll then see something like (with various metadata inside each class object):

▶ NavigationStart {...}
▶ RouteConfigLoadStart {...}
▶ RouteConfigLoadEnd {...}
▶ RoutesRecognized {...}
▶ GuardsCheckStart {...}
▶ ActivationStart {...}
▶ GuardsCheckEnd {...}
▶ ResolveStart {...}
▶ ResolveEnd {...}
▶ ActivationEnd {...}
▶ NavigationEnd {...}

That’s a lot of events! And this is a good thing.

To find the events that are relevant to our goal of creating a loading spinner, we’re going to follow a simple process of filtering out the events we do need and then toggling a loading state accordingly.

Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings

First, let’s define a new loading$ property which contains the $ suffix to denote an Observable type. We’ll then create a new Observable using of():

import { Observable, of } from 'rxjs';

@Component({...})
export class AppComponent implements OnInit {
  loading$: Observable<boolean> = of(false);

  constructor(private router: Router) {}

  ngOnInit() {
    this.router.events.subscribe(console.log);
  }
}

We can then subscribe in the template to our loading$ Observable using the Async Pipe:

<div *ngIf="loading$ | async">Loading...</div>

Our next step is to pipe() and filter() on the events we’re interested in:

import { Observable, of } from 'rxjs';
import { filter } from 'rxjs/operators';

@Component({...})
export class AppComponent implements OnInit {
  loading$: Observable<boolean> = of(false);

  constructor(private router: Router) {}

  ngOnInit() {
    this.router.events.pipe(
      filter(
        (e) =>
          e instanceof NavigationStart ||
          e instanceof NavigationEnd ||
          e instanceof NavigationCancel ||
          e instanceof NavigationError
      )
    )
    // ONLY runs on:
    // NavigationStart, NavigationEnd, NavigationCancel, NavigationError
    .subscribe(console.log);
  }
}

As Observables are lazy by design, any further logic we write will only run when these navigation events are found, instead of running on any type of router event.

So now the last step is simply to set loading$ to true when we are inside a NavigationStart and as soon as NavigationEnd, NavigationCancel or NavigationError fire we’ll set it back to false.

To do this, we can simply map() a new value back and check if the NavigationStart event is the one we’ve received. If it is, we’ll return true and false if not:

import { Observable, of } from 'rxjs';
import { filter, map } from 'rxjs/operators';

@Component({...})
export class AppComponent implements OnInit {
  loading$: Observable<boolean> = of(false);

  constructor(private router: Router) {}

  ngOnInit() {
    this.router.events.pipe(
      filter(
        (e) =>
          e instanceof NavigationStart ||
          e instanceof NavigationEnd ||
          e instanceof NavigationCancel ||
          e instanceof NavigationError
      ),
      map((e) => e instanceof NavigationStart)
    )
    // true or false
    .subscribe(console.log);
  }
}

As we are using map() we’ll return a new value to our .subscribe() each time it fires. To finish things off we’ll need to point our Observable to the this.loading$ and remove the manual subscription:

import { Observable, of } from 'rxjs';
import { filter, map } from 'rxjs/operators';

@Component({...})
export class AppComponent implements OnInit {
  loading$: Observable<boolean> = of(false);

  constructor(private router: Router) {}

  ngOnInit() {
    this.loading$ = this.router.events.pipe(
      filter(
        (e) =>
          e instanceof NavigationStart ||
          e instanceof NavigationEnd ||
          e instanceof NavigationCancel ||
          e instanceof NavigationError
      ),
      map((e) => e instanceof NavigationStart)
    );
  }
}

Nice and clean! These events will fire when navigating between your application components. For best results use a Route Guard to load the data for a particular page, as this will force the NavigationEnd event to only fire when the data has resolved.

If you are serious about your Angular skills, your next step is to take a look at my Angular courses where you’ll learn Angular, TypeScript, RxJS and state management principles from beginning to expert level.

Happy spinning!

Learn Angular the right way.

The most complete guide to learning Angular ever built.
Trusted by 82,951 students.

Todd Motto

with Todd Motto

Google Developer Expert icon Google Developer Expert

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover