Dany Paredes
Dany Paredes | Javascript / Web

Dany Paredes | Javascript / Web

How to use Angular resolvers to prefetch beers to the party

How to use Angular resolvers to prefetch beers to the party

How to use Angular resolvers to prefetch data

Dany Paredes's photo
Dany Paredes
·Oct 16, 2021·

6 min read

Subscribe to my newsletter and never miss my upcoming articles

Play this article

Table of contents

  • The Angular Party
  • What do we need to do?
  • The beer service
  • The home component
  • The BeerRoom Component
  • The ResolverRoom Component
  • The Resolver
  • Register resolver and create the routes.
  • Get the experience!!!
  • My personal opinion

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

Using a service, let's put the resolver to the test, 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 one 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.
  • Create the beer service to get the data and provide a subscription with the result.
  • Create three components, BeerRoom and ResolverRoom, and HomeComponent.
  • Create the resolver.
  • Register it and define the app routes.

Also, we include other actors 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, making a request to 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

It is the home page with 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 get 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 to it.

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'];
  }
}

Similar to 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$, also updates the type return to match with Observable.

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 resolver and create the routes.

We don't go deep into how the router works in Angular. You can read more details into 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 the two experiences:

  • The Component, you get into the room but 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

Did you find this article valuable?

Support Dany Paredes by becoming a sponsor. Any amount is appreciated!

Learn more about Hashnode Sponsors
 
Share this