Jeremy
Jeremy

Reputation: 1073

Show loading indicator in Angular while waiting for a RxJS observable

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.

Upvotes: 9

Views: 15836

Answers (4)

mexahuk
mexahuk

Reputation: 68

Try my own library ngrx-busy to display loading indicator with any observables. Unlike the ng-busy library you don't need to cast it to Promises or store Subscription. Also you don't need to care about change detection strategy.

Just register the library single time (add Http interceptor and NgrxModule) and use it like this:

<ngrx-busy>
  <div *ngIf="userDetails | async as userDetails">
    <h1>{{userDetails.firstName}} {{userDetails.lastName}}</h1>
  </div>
</ngrx-busy>
@ViewChild(NgrxBusy) busy: NgrxBusy;

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

ngrx-busy

Upvotes: 2

benshabatnoam
benshabatnoam

Reputation: 7680

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.

Upvotes: 2

martin
martin

Reputation: 96999

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

Upvotes: 10

Derviş Kayımbaşıoğlu
Derviş Kayımbaşıoğlu

Reputation: 30663

you can change your *ngIf

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

then

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

Reference

Upvotes: 10

Related Questions