DEV Community

John Carroll
John Carroll

Posted on • Edited on

Simple Angular "isLoading?" Service Library

IsLoadingService is a simple angular service for tracking whether your app, or parts of it, are loading. By subscribing to its isLoading$() method, you can easily know when to display loading indicators.

At its most basic, you can import the service into your root component and use ngIf + the AsyncPipe to show a loading indicator during page navigation.

Example:

import { IsLoadingService } from '@service-work/is-loading';

@Component({
  selector: 'app-root',
  template: `
    <mat-progress-bar
      *ngIf="isLoading | async"
      mode="indeterminate"
      color="warn"
      style="position: absolute; top: 0; z-index: 100;"
    >
    </mat-progress-bar>

    <router-outlet></router-outlet>
  `,
})
export class AppComponent {
  isLoading: Observable<boolean>;

  constructor(
    private isLoadingService: IsLoadingService,
    private router: Router,
  ) {}

  ngOnInit() {
    this.isLoading = this.isLoadingService.isLoading$();

    this.router.events
      .pipe(
        filter(
          event =>
            event instanceof NavigationStart ||
            event instanceof NavigationEnd ||
            event instanceof NavigationCancel ||
            event instanceof NavigationError,
        ),
      )
      .subscribe(event => {
        // if it's the start of navigation, `add()` a loading indicator
        if (event instanceof NavigationStart) {
          this.isLoadingService.add();
          return;
        }

        // else navigation has ended, so `remove()` a loading indicator
        this.isLoadingService.remove();
      });
  }
}
Enter fullscreen mode Exit fullscreen mode

If you want to manually indicate that something is loading, you can call the loadingService.add() method, and then call the loadingService.remove() method when loading has stopped.

If you call loadingService.add() multiple times (because multiple things are loading), isLoading$() will remain true until you call remove() an equal number of times.

Internally, the IsLoadingService maintains an array of loading indicators. Whenever you call add() it pushes an indicator onto the stack, and remove() removes an indicator from the stack. isLoading$() is true so long as there are any loading indicators on the stack.

You can also pass a subscription or promise argument to loadingService.add(subscription). In this case, the loading service will push a loading indicator onto the stack, and then automatically remove it when the subscription closes / promise resolves (i.e. you don't need to manually call remove().

Advanced Usage

For more advanced scenarios, you can call add() with an options object containing a key property. The key allows you to track the loading of different things seperately. Any truthy value can be used as a key. The key option for add() is intended to be used in conjunction with key option for isLoading$() and remove().

Example:

class MyCustomComponent implements OnInit, AfterViewInit {
  constructor(
    private loadingService: IsLoadingService,
    private myCustomDataService: MyCustomDataService,
  ) {}

  ngOnInit() {
    this.loadingService.add({key: MyCustomComponent})

    const subscription = this.myCustomDataService.getData().subscribe()

    // Note, we don't need to call remove() when calling
    // add() with a subscription
    this.loadingService.add(subscription, {
      key: 'getting-data'
    })
  }

  ngAfterViewInit() {
    this.loadingService.remove({key: MyCustomComponent})
  }

  isDataLoading(): boolean {
    // the `isLoading()` method (without the `$`) returns a boolean
    // instead of an observable
    return this.loadingService.isLoading({key: 'getting-data'})
  }
}

Enter fullscreen mode Exit fullscreen mode

Wrapping Up

This service is very simple, but I've found myself often recreating it in different applications so I decided to package it up and publish it. It also helps get around a few Angular quirks I've run into, by debouncing and deduplicating isLoading$ emissions which can otherwise mess up Angular's change detection.

Personally, I hook it up to a global loading indicator in my app's root component and let it automatically respond to router navigation. Additionally, when loading data from an API, I generally want to display that same app loading indicator, so I'll simply add the data fetch subscriptions with loadingService.add(subOne) + loadingService.add(subTwo). Though if I wanted to display a different loading indicator for data fetching, I could use the optional key argument when calling add() and isLoading$().

You can check it out on GitLab

Top comments (0)