angular async reloading spinner
Asked Answered
D

3

5

I have a simple setup to show a loading spinner when the async pipe is null:

<div *ngIf="(searchResults$ | async) as searchResults; else loading">
</div>
<ng-template #loading>
    loading..
</ng-template>

However, when the user searches again for a second time, the loading.. doesn't show, I suppose I need this searchResults$ observable to emit null to show the spinner again, or have a separate isLoading variable.

what's the best way of doing that?

if it matters, i've got a debounce and a switchMap (i.e. tricky using finalize etc)

this.searchResults$ = this.filters$
      .pipe(
        debounceTime(200),
        distinctUntilChanged(),
        switchMap((f) => {
            return httpGet(f)
        })
      )

also, I tried *ngIf="!isLoading && (searchResults$ | async) as searchResults but found it problematic, e.g. searchResults$ not subscribed to, or angular complaining about changes after change detection

Drinking answered 17/2, 2019 at 21:46 Comment(4)
Can you supply your code using the isLoading solution? The issues you mentioned should be solvable and I think it makes for a clearer solution than have the searchResults observable emit nulls to indicate loadingStingaree
@Stingaree inside the switchmap, set this.isLoading = true, then httpGet(f).pipe( set isLoading=false ). tried putting isLoading in the template both before and after the pipe to async.Drinking
The problem may be that returning httpGet(f).pipe(), depending on what is inside the pipe, may be discarding the return of httpGet(f). Like I said, I am not a fan of having searchResults$ emit nulls as I think it could erode code clarity, but if you're happy with that solution I will leave it at thatStingaree
@Stingaree thanks - yeah it's not ideal having to emit nulls I agree. I'd like to see a nicer solution one day in the future, as this is something I end up needing multiple times per project.Drinking
D
0

I now use the ngx-http-request-state library which gives loading, error and render states. It also can work on reloads if you use scan() or withLatestFrom(). Also it works from inner observables.

If using graphql, I consider using a combination of options of fetchPolicy, loading, and networkStatus.

Drinking answered 2/9, 2022 at 21:5 Comment(0)
S
4

You could try setting the isLoading variable using the tap operator like so:

this.searchResults$ = this.filters$
      .pipe(
        debounceTime(200),
        distinctUntilChanged(),
        tap(() => {this.isLoading = true}),
        switchMap((f) => {
            return httpGet(f)
        }),
        tap(() => {this.isLoading = false})
      );

You can then get around angular not subscribing to your observable by hosting it in a different *ngIf inside a ng-container element.

<ng-container *ngIf="(searchResults$ | async) as searchResults">
  <div *ngIf="!isLoading"></div>
</ng-container>
<ng-template *ngIf="isLoading">
    loading..
</ng-template>
Stingaree answered 17/2, 2019 at 22:59 Comment(2)
one more thing is error catching and setting isLoading to false upon error. Something the other solution didn't take into account either. see codinglatte.com/posts/angular/angular-async-pipe-handle-errorsDrinking
@robertking You can add a catchError call to the pipe that sets isLoading to false, you probably also want an isError variable to be set here (and unset in the first tap call) so you can display some sort of error messageStingaree
U
2

I've faced the same issue and solved distinguishing the "ask" stream and the "result" stream, merging both for the component result observable. Something like this (based on your code):

this.searchResults$ = merge(
      this.filters$.pipe(map(f => null)),
      this.filters$.pipe(
        debounceTime(200),
        distinctUntilChanged(),
        switchMap((f) => {
            return httpGet(f)
        })
      )
    );
Urga answered 17/2, 2019 at 21:59 Comment(0)
D
0

I now use the ngx-http-request-state library which gives loading, error and render states. It also can work on reloads if you use scan() or withLatestFrom(). Also it works from inner observables.

If using graphql, I consider using a combination of options of fetchPolicy, loading, and networkStatus.

Drinking answered 2/9, 2022 at 21:5 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.