🇺🇸 EN
Frontend
·7 min read·↗·🇺🇸 EN

Cómo usar Resolvers en Angular

Portada principal del artículo: Cómo usar Resolvers en Angular

Cuando vamos a una fiesta, me encanta tener todas las cervezas listas para tomar una, pero tener que esperar a que las saquen de la nevera a la mesa no es una buena experiencia.

Lo mismo les pasa a los usuarios con nuestras aplicaciones en Angular: mostramos el componente, pero los datos todavía no están listos o la petición HTTP tarda y ellos se quedan esperando.

Una buena solución es mostrar un indicador de carga hasta recibir la respuesta. Pero, ¿qué pasa si mis usuarios quieren tener las cervezas listas desde el inicio? No hay problema, porque Angular tiene una solución: los Resolvers.

Los Resolvers nos ayudan a precargar datos antes de que el enrutador termine la navegación, iniciando la transición entre componentes y guardando esa información.

Son muy útiles para evitar mostrar un componente vacío y asegurar que tengamos nuestros datos listos antes de pasar a la vista.

La fiesta de Angular

Vamos a poner a prueba el resolver usando un servicio y construyendo una pequeña aplicación para mostrar una lista de cervezas desde la API https://api.punkapi.com/v2/beers.

La aplicación tiene dos rutas, ComponentRoom y ResolverRoom, y cada una ofrece una experiencia de usuario diferente:

  • ComponentRoom usa el pipe async para obtener los datos desde el servicio.

  • ResolverRoom usa un resolver para precargar los datos y el componente accede a ellos a través de route.snapshot.data.

¿Qué necesitamos hacer?

Iremos paso a paso:

  • Crear una interfaz para mapear la respuesta de la API.

  • Proveer un servicio de cervezas para solicitar los datos y devolver un observable con el resultado.

  • Crear tres componentes: BeerRoom, ResolverRoom y HomeComponent.

  • Implementar el resolver.

  • Registrar el resolver y definir las rutas de la aplicación.

También incluiremos otros elementos clave como Router, ActivatedRoute, Observable, etc. ¡Vamos a ponerlo a funcionar!

El código fuente está en https://github.com/danywalls/how-prefech-with-resolver

El servicio de cervezas

Creamos una interfaz llamada Beer y un servicio BeerService para obtener los datos de la API.

La interfaz Beer define algunas de las propiedades que recibimos en la respuesta de la API.

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

BeerService requiere inyectar HttpClient, hacer la petición a la API y apoyarse en RxJS para devolver un observable con un arreglo de Beer.

Importamos HttpClient y el decorador Injectable. Luego creamos el método getBeers para retornar el resultado de la consulta a https://api.punkapi.com/v2/beers. Además, usamos el operador delay para simular una respuesta lenta de unos segundos:

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));
 }
}

Puedes leer más sobre operadores y servicios en: Delay Operator https://www.learnrxjs.io/learn-rxjs/operators/utility/delay y Services https://angular.io/tutorial/toh-pt4

El componente Home

La página de inicio tiene dos enlaces para acceder a las rutas beer-room y resolver-room, utilizando la directiva 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>

Más sobre routerLink en https://angular.io/api/router/RouterLink

El componente BeerRoom

Este componente obtiene los datos desde el servicio de cervezas y resuelve la suscripción directamente en la plantilla. Declaramos la variable beers$ como un observable y le asignamos el observable de nuestro servicio:

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$;
  }
}

Dentro de la plantilla, usamos el pipe async para esperar a que termine la suscripción:

    <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>

Revisa más sobre directivas y pipes en: ngIf https://angular.io/api/common/NgIf, ngFor https://angular.io/api/common/NgForOf y Pipe Async https://angular.io/api/common/AsyncPipe

El componente ResolverRoom

A diferencia del componente anterior, aquí inyectamos ActivatedRoute, que nos proporciona los datos guardados en el snapshot por el resolver.

Guardamos el valor del snapshot en la variable beerRouterList:

Más adelante veremos cómo configurar el resolver en la definición de rutas.

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

Al igual que en BeerRoomComponent, iteramos sobre el arreglo de cervezas con la directiva ngFor:

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

Listo. Los siguientes pasos consisten en crear el resolver y configurarlo en las rutas.

El Resolver

La pieza fundamental es el resolver. BeerResolverService implementa la interfaz Resolve. El resolver funciona como un proveedor de datos que el enrutador ejecuta durante el proceso de navegación, esperando a que se complete antes de activar el componente.

Implementamos el método resolve. Al igual que en el componente, inyectamos BeerService y retornamos el observable beers$, asegurando que el tipo de retorno coincida con 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$;
  }
}

Registrar el resolver y crear las rutas

No entraremos en detalles sobre el funcionamiento interno del router en Angular (puedes consultar la documentación oficial para ver más detalles), pero aquí definimos las rutas de nuestra aplicación:

  • La ruta home carga HomeComponent.

  • La ruta beer-room carga BeerRoomComponent.

  • La ruta resolver-room carga el componente con un detalle clave: usa la propiedad resolve para obtener los datos provistos por el resolver y guardarlos en route.snapshot.data bajo la clave beers con el valor emitido por la suscripción.

  • La ruta por defecto ('') redirige cualquier petición hacia home.

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

La experiencia en acción

Listo, tenemos dos experiencias de usuario:

  • En ComponentRoom entras a la vista de inmediato, pero las cervezas aún no están listas (el usuario ve la pantalla vacía o esperando).

  • En ResolverRoom el resolver te permite pasar a la vista únicamente cuando los datos ya están listos.

Mi opinión personal

Si tu vista solo necesita cargar un único valor o recurso principal, me gusta usar el resolver.

Por el contrario, si mi componente realiza múltiples peticiones HTTP independientes, prefiero manejar los datos dentro del propio componente. De esta forma, el usuario empieza a recibir información de inmediato mientras el resto termina de cargar.

¿Qué opción crees que es mejor para tus usuarios? ¡Prueba ambas y evalúa la experiencia!

Ver demo en vivo

Espero que este artículo te sirva de ayuda para entender cómo y cuándo usar resolvers. Si te gustó, ¡compártelo!

Foto por Meritt Thomas en Unsplash

Parte de la serie Frontend

Modern frontend development — Angular, React, TypeScript, accessibility, and performance.

Ver serie completa

Artículos Relacionados

Compartir este artículo

Si esta guía te resultó útil, compártela con tu equipo o con otros desarrolladores.


Software real. Lecciones reales.

Comparto las lecciones que aprendí por las malas, para que puedas evitarlas o estar preparado cuando ocurran.

User avatar
User avatar
User avatar
User avatar
+13K

Únete a 13,800+ desarrolladores y lectores.

Cero spam. Cancela tu suscripción en cualquier momento.