Better Angular Resolvers using nested Observables or “how to show loading indicators, show error messages and update resolved data”

Phillip Kessels
spacepilots
Published in
2 min readMay 30, 2019

Angular’s Resolvers are a nice, declarative way of getting data into your components. However when used as described in the Angular guide they quickly stretch their limits. We suggest a simple and elegant way on how to make resolvers more flexible including the ability to respond to new data (e.g. when resolved data changes in a cache).

Example

In our shift scheduling application tift we interface a GraphQL API from our frontend written in Angular.

When someone accesses a so called “Plan” we need to load it from the server. Our GraphQL client is Apollo Client which includes a powerful caching mechanism.

A plan can be edited and is then automatically updated in the Apollo Cache. The following is a classic example with resolvers:

// plan-resolver.service.ts
// ...
export class PlanResolverService implements Resolve<Plan> {
// ...resolve(route: ActivatedRouteSnapshot): Observable<Plan> {
return this.client.get(...); // get data from server as observable;
}
}

This yields the following problems:

  • we cannot simply show a loading indicator, because

The Router guards require an observable to complete, meaning it has emitted all of its values — Angular Routing & Navigation Guide

The Router does not finish navigating to the route until the data is resolved and only then your component is shown. You might even need to use rxjs’s take(n) here so that your source Observable completes, see https://angular.io/guide/router#resolve-pre-fetching-component-data.

  • we might want to use an Observable which possibly emits multiple times (e.g.) when the data is changed from polling, server-sent events or a frontend cache and does not complete.
  • an Observable which errors fails the resolver and the component also won’t load

There are solutions to this problem which imperatively hook into Router Events. These make you unify your logic into one place and possibly violate modulation by leaking responsibilities across components. Instead we propose to use return an Observable of an Observable .

Solution

// plan-resolver.service.ts
// ...
export class PlanResolverService implements Resolve<Observable<Plan>> {
// ... resolve(route: ActivatedRouteSnapshot): Observable<Observable<Plan>> {
return of(this.client.get(...));
}
}

In this example note how we use of to construct an Observable<Observable<Plan>>. This Observable immediately completes so that the resolver allows the router to finish navigating and loading the desired component. We can then use the Observable in the component, including handling errors and showing a loading indicator:

export class DashboardComponent {
loading = true;
error = false;
plan$: Observable<ShowPlan.Plan>;
constructor(private activatedRoute: ActivatedRoute) {
this.plan$ = this.activatedRoute.data.pipe(switchMap((data) => data.plan$));
this.plan$.subscribe(() => this.loading = false, () => this.error = true);
}
}

This component will also naturally respond to changes from the source Observable , e.g. when the cache updates the plan.

--

--