Search code examples
angularrxjsreactive-programmingrxjs6

Request in async pipe is triggered only when preceding condition is true


I have an ngIf in my template something like:

<div *ngIf="selectedStudents?.length > 0 && persistedPreferences | async as studentPreferences; else someTemplate">

Code currently is part reactive and part imperative. I'm migrating from imperative to declarative/reactive paradigm.

selectedStudents is just a regular array of objects that is initialized imperatively by subscribing manually to response received from backend (can't heavily refactor now). However, persistedPreferences is an observable that will get response from http request (using async pipe). This part is reactive/declarative.

Since, persistedPreferences will be subscribed by async pipe, its http request will fire only when the first condition is true. This means that I will have to first wait for selectedStudents response to be received and only then persistedPreferences request will be fired.

So, as a user, I end up waiting slighty longer (on slower network). Any idea as to how this can be handled? Any hints?

Note: We need to wait for both requests to finish before user sees anything. Moreover. data from both goes to two different components.

I can think of a few alternatives:

  1. move async thing before the other condition for now (not sure how!)
  2. Maybe modify a little - make selectedStudents an observable and combine it with persistedPreferences into a single observable and use it in template?

Something like this:

this.combinedObs = combineLatest([selectedStudentsObs, persistedPreferences]).pipe(
   map(([res1, res2] => { students: res1, prefs: res2 }))
)

and then use it like:

*ngIf="combinedObs  | async as combinedResult"
<comp1 [students]="combinedResult.students"></comp1>
<comp2 [prefs]="combinedResult.prefs"></comp2>

But the second way doesn't seem to be a such a good solution. Thinking about performance and error handling. Selected students is the real important data and an http error need to be informed vaguely to the user. Preferences are just optional thing (even if their http request has some error, we can still show selected students).


Solution

  • The second way would be elegant one that triggers both HTTP requests in parallel. However since you mention both are HTTP requests, a forkJoin would be a better fit instead of combineLatest.

    You could handle errors, or perform any side-effects in the component controller.

    Controller (*.ts)

    import { forkJoin, throwError } from 'rxjs';
    import { tap, catchError } from 'rxjs/operators';
    
    combineObs$: Observable<any>;
    selectedStudents$: Observable<any>;
    prefs$: Observable<any>;
    
    this.selectedStudents$ = this.http.get('url').pipe(
      tap((students: any) => {            // performing side-effects
        this.selectedStudents = students;
        // do something else
      }),
      catchError((error: any) => {
        // show error to the user
        return throwError(error);         // `catchError` must return an observable
      })
    );
    
    this.prefs$ = this.http.get('url');
    
    this.combinedObs$ = forkJoin({
      students: this.selectedStudents$,
      prefs: this.prefs$
    });
    

    Template (*.html)

    <ng-container *ngIf="(combinedObs$ | async) as combinedResult">
      <comp1 [students]="combinedResult.students"></comp1>
      <comp2 [prefs]="combinedResult.prefs"></comp2>
    </ng-container>