Entendiendo los Route Resolvers de Angular

winding-road-between-forest

Desarrollar una aplicación del mundo real con múltiples llamadas al servidor puede estar lleno de errores. Si estás aquí, significa que has luchado con el retraso de las llamadas a la API. Estos retrasos pueden causar una UX negativa. Hoy vamos a entender los Route Resolvers en Angular 8. Hay varias cosas diferentes que podemos hacer para mejorar la experiencia del usuario, como mostrar un indicador de progreso. Para ceñirnos al tema, vamos a ver qué es un Route Resolver y qué podemos conseguir con él.

¿Qué es un Route Resolver de Angular?

Un Resolver es una clase que implementa la interfaz Resolve de Angular Router. De hecho, Resolver es un servicio que tiene que estar en el módulo raíz. Básicamente, un Resolver actúa como un middleware, que puede ser ejecutado antes de que se cargue un componente.

También te puede gustar: Angular Resolvers.

¿Por qué usar Route Resolvers en Angular?

Para explicar cómo se puede usar un resolver en Angular, pensemos en el escenario cuando estás usando *ngIf="some condition", y tu lógica se basa en la longitud de un array, que se manipula cuando se completa una llamada a la API. Por ejemplo, puedes querer mostrar en un componente los ítems de este array que acaban de ser obtenidos en una lista desordenada.

<ul><li *ngFor="let item of items">{{item.description}}</li></ul>

En esta situación, podrías tener un problema porque tus datos aparecerán después de que el componente esté listo. Los elementos en el array no existen realmente todavía. Aquí, el Route Resolver es muy útil. La clase Route Resolver de Angular obtendrá tus datos antes de que el componente esté listo. Tus sentencias condicionales funcionarán sin problemas con el Resolver.

Interfaz Resolver

Antes de empezar a implementar un Route Resolver, veamos primero cómo es la interfaz Resolver.

export interface Resolve<T> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | T { return 'Data resolved here...' }}

Si quieres crear un Route Resolver, tienes que crear una nueva clase que implemente la interfaz anterior. Esta interfaz nos proporciona una función resolve, que te obtiene dos parámetros en caso de que los necesites. El primero es la ruta, que es de tipo ActivatedRouteSnapshot, y el segundo es el estado, de tipo RouterStateSnapshot. Aquí, puedes hacer una llamada a la API que obtendrá los datos que necesitas antes de que se cargue tu componente.

A través del parámetro de la ruta, puedes obtener parámetros de la ruta que pueden ser utilizados en la llamada a la API. El método resolve puede devolver un Observable, una promesa o simplemente un tipo personalizado.

Nota: Es importante mencionar que sólo se devolverán datos resueltos a través de este método.

¡Este es un error que mucha gente cometerá al usar resolvers por primera vez! Así que tienes que completarlos antes de enviarlos al router.

Enviando datos al router a través de un resolutor

Déjame mostrarte lo que quiero decir en la sección anterior. Digamos que tienes un método que devuelve un observable:

items: any = ;getData(): Observable<any> { const observable = Observable.create(observer => { observer.next(this.items) }); return observable;}

Entonces, ahora, verás que la suscripción que tenemos, nunca llega. Esto se debe a que no está enviando los datos correctamente. No se está completando la suscripción. Para solucionar ese error, tienes que completar la suscripción añadiendo una línea más de código.

observer.complete();

En caso de que estés devolviendo una promesa, tienes que resolverla antes de enviar los datos al router.

Implementación de un Route Resolver

Después de que hayamos terminado con la explicación básica y el por qué y el cómo usar un Route Resolver, vamos a empezar a implementar uno. En este breve ejemplo, asumo que estás familiarizado con los comandos CLI de Angular y cómo crear un nuevo proyecto, por lo que demostraré sólo el código obligatorio. Puedes encontrar el proyecto de demostración a través del repositorio de GitHub al final del artículo.

En este ejemplo, usaré jsonplaceholder como una API falsa para obtener los datos del usuario para demostrar las llamadas a la API con resolvedores de rutas.

En primer lugar, necesitaremos un servicio que obtenga los datos del usuario por nosotros. En este servicio, tenemos una función llamada getUsers() que devuelve un observable.

@Injectable({providedIn: 'root'})export class FakeApiService { constructor(private http: HttpClient) { } private usersEndpoint = "https://jsonplaceholder.typicode.com/users"; getUsers(): Observable<any> { // We do not subscribe here! We let the resolver take care of that... return this.http.get(this.usersEndpoint); }}

Es importante no suscribirse a la función getUsers. El resolvedor de rutas llamado UserResolver se encargará de esto por ti. El siguiente paso es crear un nuevo servicio llamado UserResolver que implementará la función resolve de la interfaz Resolve del router.

@Injectable({providedIn: 'root'})export class UserResolverService implements Resolve<any> { constructor(private fakeApi: FakeApiService) { } resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { return this.fakeApi.getUsers().pipe( catchError((error) => { return empty(); }); ); }}

Este servicio, UserResolver, se suscribirá automáticamente al observable getUsers y proporcionará al router los datos obtenidos. En caso de un error, mientras que la obtención de los datos, puede enviar un observable vacío y el router no procederá a la ruta.

La navegación terminará en este punto. Este último paso consiste en crear un componente que será llamado cuando el usuario vaya a la ruta /users. Normalmente, sin un Resolver, tendrá que obtener los datos en el hook ngOnInit del componente y manejar los errores causados por «no hay datos». El componente del usuario es sencillo. Sólo obtiene los datos del usuario desde el ActivatedRoute y los muestra en una lista desordenada.

Después de haber creado el componente del usuario, necesitas definir las rutas y decirle al router que use un resolver ( UserResolver). Esto podría lograrse con el siguiente código en el app-routing.modulte.ts.

const routes: Routes = ;@NgModule({ imports: , exports: })export class AppRoutingModule { }

Necesitas establecer la propiedad resolver en la ruta del usuario y declarar el UserResolver. Los datos se pasarán a un objeto con una propiedad llamada usuarios. Después de eso, usted está casi listo. Sólo queda una cosa por hacer. Debe obtener los datos obtenidos en el componente de los usuarios a través del ActivatedRoute con el siguiente código.

constructor(private activatedRoute: ActivatedRoute) { }users: any;ngOnInit() { this.activatedRoute.data.subscribe((data: { users: any }) => { this.users = data.users; });}

Entonces, puede simplemente mostrarlos en HTML sin ninguna declaración *ngIf ( *ngIf="users && users.length > 0 ) porque los datos estarán allí antes de que se cargue el componente.

<h2>Fetched Users:</h2><ul><li *ngFor="let user of users">{{ user.name }}</li></ul>

Resumen

Cerrando este artículo sobre los Route Resolvers, me gustaría señalar una vez más las ventajas que nos proporcionan. En primer lugar, nos evitan las molestas comprobaciones que hay que hacer a nivel de HTML para no tener problemas hasta la recepción de nuestros datos. En segundo lugar, nos permiten centrarnos más en la experiencia del usuario, ya que podemos detener la navegación si se produce un error en los datos mientras se obtienen, sin tener que cargar el componente de vista previa. También podemos hacer más cosas antes de completar la navegación, como adjuntar más lógica o mapear los datos antes de acceder al componente cargado.

El código de la demo está disponible en GitHub.

Más lecturas

  • Tutorial de Angular: Angular 7 y el Framework RESTEasy.
  • Construyendo una aplicación Angular 5 paso a paso.
  • Aplicación Angular 7 + Spring Boot: Ejemplo de Hola Mundo.

Deja un comentario