Show loading indicator in Angular while waiting for a RxJS observable

10,204

Solution 1

If you just want the content to disappear you can emit for example null when you start fetching a different user:

this.userDetails = this.userId.asObservable().pipe(
  switchMap(id => merge(
    of(null),
    this.getUserDetails(id)
  )),
);

The merge Observable creation method will reemit emit null and then wait until getUserDetails completes. You don't even need ng-busy for this.

Your updated demo: https://stackblitz.com/edit/angular-ng-busy-vwteyf?file=src/app/app.component.ts

Solution 2

you can change your *ngIf

ngIf="userDetails | async as userDetails else #loading"

then

<div #loading>
  loading...
</div>

Reference

Solution 3

You can use ng-busy (or basically any other npm component out there) for displaying a loading indicator in each http call (promise or observable in your case) in your app.

EDIT: Regarding the fact that you're using Observable, you can use .toPromise in order to work with ng-Busy. Modified the DEMO to show you how to do that.

Here is a Minimal, Complete, and Verifiable example of ng-busy with your code.

Share:
10,204
Jeremy
Author by

Jeremy

Updated on June 13, 2022

Comments

  • Jeremy
    Jeremy over 1 year

    I have an Angular component that listens for a route parameter for a user id to change and when it does it loads the user details. The user details takes a few seconds to return data from the API.

    If i'm viewing details for User A and then click to view details on User B, it continues to show User A until User B details are returned a few seconds after my click. Is there a way I can show a loading indicator or just blank it out while it's retrieving data for User B?

    User details component:

    ngOnInit(): void {
      this.userDetails = this.route.paramMap.pipe(
        switchMap((params: ParamMap) => this.userService.getUserDetails(+params.get('userId')))
      );
    }
    

    User details template:

    <div *ngIf="userDetails | async as userDetails">
      <h1>{{userDetails.firstName}} {{userDetails.lastName}}</h1>
    </div>
    

    I would like the user details div to either be blank or show some sort of loading indicator if that inner switchMap is currently running. I know one option would be to have a loading variable that I set to true before the switchMap and false after the switchMap and use that in the *ngIf of the div, but I'm hoping there was a slicker way to not have to have loading variables for EVERY one of these situations.

    I have an example StackBlitz: https://stackblitz.com/edit/angular-ng-busy-yxo1gu

    The goal is when I click the User B button, User A information should disappear while User B is loading. I have dozens of this scenario in my app so I'm looking for the cleanest way to do this.

  • Jeremy
    Jeremy almost 5 years
    This works for loading the first user. But when I click on a different user it continues to show the first user's information until the new user has finished loading since the component is reused.
  • Derviş Kayımbaşıoğlu
    Derviş Kayımbaşıoğlu almost 5 years
    if you assign this.userDetails = null before you start async operation, it will be undefined that moment, so it falls to else block
  • Jeremy
    Jeremy almost 5 years
    That's pretty close, but my userDetails is an observable. I took your stackblitz and modified it closer to what I have with userDetails. I have dozens of this scenario throughout my app so I'm looking for the slickest way to remove User A details after the user clicks on the User B button. stackblitz.com/edit/angular-ng-busy-yxo1gu
  • benshabatnoam
    benshabatnoam almost 5 years
    @Jeremy did you see the updated DEMO? did it help you?
  • Florian Leitgeb
    Florian Leitgeb about 4 years
    I get this error in my template: Parser Error: Unexpected token # at column 76
  • Hasintha Abeykoon
    Hasintha Abeykoon over 2 years
    @FlorianLeitgeb this might help beginners, so I decided to add this comment, Angular ngIf directive else block only expects the template name, so you cannot place # here with the template name. it should be ngIf="userDetails | async as userDetails else loading" , Also I think you should use <ng-template #loading>...</ng-template> to setup your loading block in html.