How To Use Angular Resolvers

How To Use Angular Resolvers

Optimizing Your Angular App with Data Prefetching and Resolvers

When we go to a party, I love to have all beers ready to drink and take one, but sometimes taking time to pick from the fridge to the table and stay waiting is not a good experience.

The same happens with our users working with our angular apps; we show the component, but the data is not there, or the HTTP request takes time, and they are waiting.

A good solution is to show a loading until I get the data, but if my users want the beers ready from the beginning? No problem because Angular has a solution, The Resolvers.

The Resolvers help us prefetch data before the router finishes, start the transition between components, and store it.

The resolvers are helpful to avoid showing an empty component and have our data ready before moving to the component.

The Angular Party

Let's put the resolver to the test using a service and build an app to show a list of beers from api.punkapi.com/v2/beers API.

The app has two routes, ComponentRoom and ResolverRoom; each has a different user experience.

  • The Component Room uses the pipe async to get the data from the service.

  • The Resolver Room uses a resolver to get the data and the component access utilizing the route.snapshot.data.

What do we need to do?

We will be going step by step:

  • Create an interface for mapping the API response.

  • Provide a beer service to get the data and a subscription with the result.

  • Three components, BeerRoom and ResolverRoom, and HomeComponent.

  • Use the resolver.

  • Register it and define the app routes.

Also, we include other actors such as Router, ActivateRoute, Observable, etc. But let it works!

The source code is in https://github.com/danywalls/how-prefech-with-resolver

The beer service

We create an interface, Beer, and a service BeerService, to provide the data from the API.

The Beer interface has some properties of the beer API response.

export  interface  Beer {
   id: number;
   name: string;
   tagline: string;
   first_brewed: string;
   description: string;
   image_url: string;
 }

The BeerService requires injecting the httpClient, requesting the API, and using Rxjs to return an observable array of Beer.

We import httpClient and Injectable decorator and create the getBeers method to return the request result to api.punkapi.com/v2/beers; also, using the delay operator makes the response slow for 5 seconds.

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { delay } from 'rxjs/operators';
import { Beer } from './models/beer';

@Injectable()
export class BeerService {
 public beers$: Observable<Beer[]>;
 constructor(private http: HttpClient) {
   this.getBeers();
 }
 private getBeers(): void {
   this.beers$ = this.http
     .get<Beer[]>('https://api.punkapi.com/v2/beers')
     .pipe(delay(4000));
 }
}

Read more about operators and services Delay Operator learnrxjs.io/learn-rxjs/operators/utility/d.. Services angular.io/tutorial/toh-pt4

The home component

The home page has two links to access routes beer-room and resolver-room, using the directive routerLink.

  <p class="text-center">
    Do you want to join to party and wait for the beers, or when you get in, the
    beers are ready ?
  </p>
  <div class="btn-group btn-group-block">
    <a [routerLink]="['/beer-room']" class="btn btn-primary">Component Room</a>
    <a [routerLink]="['/resolver-room']" class="btn btn-secondary"
      >Resolver Room</a
    >
  </div>

More about router link angular.io/api/router/RouterLink

The BeerRoom Component

The component Room gets the data from the beer service and resolve the subscription into the template. We declare the variable beers as observable and assign the observable from our service.

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { BeerService } from '../../beer.service';
import { Beer } from '../../models/beer';

@Component({
  selector: 'app-beer-room',
  templateUrl: './beer-room.component.html',
})
export class BeerRoomComponent {
  public beers$: Observable<Beer[]>;
  constructor(private beerService: BeerService) {
    this.beers$ = beerService.beers$;
  }
}

Into the template, use the pipe async to wait until the subscription finish.

    <div *ngIf="beers$ | async as beers">
      <div class="chip" *ngFor="let beer of beers">
        <img [src]="beer?.image_url" class="avatar avatar-sm" />
        {{ beer.name }}
      </div>
    </div>

Read more directives and pipes. ngIf angular.io/api/common/NgIf ngFor angular.io/api/common/NgForOf Pipe Async angular.io/api/common/AsyncPipe

The ResolverRoom Component

Like the beer component, we inject ActivateRoute, which provides the data in the snapshot stored by the resolver, into the beer variable.

We saved the value of the snapshot beer into the beerRouterList variable.

You will see how we configure the resolver in the route configuration.

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Beer } from '../../models/beer';

@Component({
  templateUrl: './resolver-room.component.html',
})
export class ResolverRoomComponent implements OnInit {
  beerRouterList: Beer[];
  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    this.beerRouterList = this.route.snapshot.data['beers'];
  }
}

Like the BeerComponent, we iterate over the beer array using the ngFor directive.

<div class="chip" *ngFor="let beer of beerRouterList">
      <img [src]="beer?.image_url" class="avatar avatar-sm" />
      {{ beer.name }}
    </div>

Done; the following steps are creating the resolver and configuring it with the route configuration.

The Resolver

The critical player is the resolver. The BeerResolverService implements the Resolve interface. The resolver works as a data provider used by the router to resolve during the navigation process, and the router, please wait for it to complete before it gets activated.

It implements the resolve methods, same as the component we inject the beerService and return the observable beers$, and also updates the type return to match Observable<Beer[]>.

import { Injectable } from '@angular/core';
import {
  ActivatedRouteSnapshot,
  Resolve,
  RouterStateSnapshot,
} from '@angular/router';
import { Observable } from 'rxjs';
import { BeerService } from '../beer.service';
import { Beer } from '../models/beer';

@Injectable()
export class BeerResolverService implements Resolve<Observable<Beer[]>> {
  constructor(private beerService: BeerService) {}
  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<Beer[]> {
    return this.beerService.beers$;
  }
}

Register the resolver and create the routes.

We don't go deep into how the router works in Angular. You can read more details in the official documentation, but here define two routes for our app.

  • The path home, load the HomeComponent.

  • The path beer-room, load the BeerRoomComponent.

  • The path resolve-room load the component, but with a particular case, it uses the resolve to find the data provided by the resolver and store into the beer variable beers and store into the route.snapshot.data with the key beers and the value returned by the subscription.

  • The absolute path: ''' redirect any request to the home component.

const routes: Routes = [
  {
    path: 'home',
    component: HomeComponent,
  },
  {
    path: 'beer-room',
    component: BeerRoomComponent,
  },
  {
    path: 'resolver-room',
    component: ResolverRoomComponent,
    resolve: { beers: BeerResolverService },
  },
  { path: '', redirectTo: '/home', pathMatch: 'full' },
];

Get the experience!!!

Ready, we have two experiences:

  • In The Component, you get into the room but are not beer ready.

  • The resolve allows you to move to the area only when it is ready.

My personal opinion

If you have your room is getting a single value, I like to use the resolver.

But If my component has multiple requests, I like to resolve the data into the component, because the user starts to get results.

What do you think is better for our users? Play with it and get your feeling!

Click demo

Hopefully, that will give you a bit of help with how and when to use resolver. If you enjoyed this post, share it!

Photo by Meritt Thomas on Unsplash