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:
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).
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>