Actualizado el 13/07/2018: Amplia revisión del código para usar convenciones más nuevas. Actualizados Angular y RxJS a las últimas versiones disponibles, movidos los ejemplos a StackBlitz.
El objetivo de este post es demostrar cómo funcionan las resoluciones de ruta de Angular en 10 minutos, junto con proporcionar código de ejemplo en Stackblitz. Si ya estás familiarizado con las resoluciones de ruta de AngularJS, recomiendo saltarse la introducción y saltar directamente a la sección de la aplicación de ejemplo.
Tabla de contenidos
Intro
Sample App
- Paso 1: Crear una clase Resolver
- Paso 2: Añadir un Resolve Guard a la Ruta
- Paso 3: Obtener los datos resueltos de la ruta activada del componente
Notas &Consejos
- Evitar las llamadas redundantes a la API
- Múltiples Resolves en la misma ruta
- Pasar los parámetros de la ruta al resolutor
Notas de cierre
Intro
Las resoluciones de ruta no son más que una forma de preobtener los datos que un componente necesita antes de ser inicializado. Normalmente estos datos provienen de una API. Digamos que tienes un componente cuya única función es mostrar un gráfico de las ventas diarias del mes; No tiene sentido renderizar la vista o cargar este componente antes de que los datos de las ventas estén disponibles. De hecho, muchas bibliotecas de gráficos arrojarán un error si intenta inicializar un gráfico antes de suministrarle datos (y también lo hará *ngFor
). Por supuesto, puede solucionar fácilmente este problema ocultando el html con un *ngIf
o suministrando temporalmente una matriz vacía hasta que se carguen los datos necesarios. Sin embargo, mientras que usted puede conseguir sin resuelve, la implementación de ellos ayuda a hacer el código más legible y maintenable por:
- Eliminar el desorden confuso en la plantilla de su componente y el código.
- Aclarar su intención mostrando qué datos deben ser pre-fetchados.
A pesar de estos beneficios, muchos sitios evitan el uso de resoluciones en favor de mostrar la mayor parte del componente y mostrar un spinner en las secciones para las que los datos aún se están cargando. En algunos casos, este enfoque es deseable desde el punto de vista de la UX, y una resolución no debe ser utilizada. Utiliza tu criterio.
Aplicación de ejemplo
Abre la aplicación de ejemplo
Como puedes ver, nuestro punto de partida es una aplicación muy básica con dos rutas – «Inicio» y «Noticias». Se navega entre las rutas haciendo clic en la pestaña correspondiente. Vamos a añadir una resolución para pre-obtener noticias de una API antes de que se cargue el componente de Noticias. Esto tomará tres pasos:
Paso 1: Crear una clase resolver que hace una llamada Http para pre-obtener los datos que necesitamos.
Crea un nuevo archivo Typescript dentro de la carpeta app
y nómbralo:
news-resolver.service.ts
A continuación, copia y pega el siguiente código en tu nuevo archivo (explicado a continuación):
Desglosemos lo que hicimos en el código anterior:
-
Agrega declaraciones de importación ES6 para traer los módulos necesarios.
-
Creamos una nueva clase TypeScript
NewsResolver
. -
Añadimos la interfaz
Resolve
a nuestra clase – esto es OPCIONAL, pero cualquier clase que pensemos usar como resolvedor debe implementar un método resolve, así que es una buena convención. -
Añadimos un método
resolve()
aNewsResolver
– este es el método responsable de devolver los datos que necesitamos. Nombrar el método que devuelve los datos para una guardia de resolución «resolver» NO es opcional – el resolver no funcionaría si el método se llamara de otra manera. -
Si te has dado cuenta de que se está utilizando incorrectamente una solicitud
POST
en lugar de unaGET
en el código anterior, tienes toda la razón; En una aplicación real, esto sería una solicitudGET
. Los ejemplos aquí se aprovechan de, que es un sitio que proporciona puntos finales de API de prueba.
Antes de seguir adelante, debemos incluir la clase resolver que acabamos de crear en nuestro módulo de enrutamiento. Navega hasta app-routing.module.ts
y añade NewsResolver
a la matriz providers
. O, si estás empezando a trabajar con Angular 2, simplemente reemplaza el contenido de app-routing.module.ts
con el código de abajo – los cambios están marcados con notas:
Hecho esto, ya hemos definido la clase resolver. En los siguientes pasos, la añadiremos a nuestra ruta.
Código después de añadir la clase resolver
Paso 2: Añadir un Resolve Guard a la ruta.
En app-routing.module.ts
, cambia la siguiente línea de código { path: 'news', component: NewsComponent }
por:
{ path: 'news', component: NewsComponent, resolve: { news: NewsResolver }}
Todo lo que hicimos aquí fue añadir el resolve guard que acabamos de definir a nuestra ruta news
. Esto le dice a Angular que debemos esperar a que el método resolve()
de NewsResolver
devuelva datos antes de mostrar el NewsComponent
.
Es importante señalar que news
en la línea news: NewsResolver
de código es lo que elegí para nombrar cualquier dato que devuelva el resolver. Puedes llamarlo como quieras.
Como nota tangencial – si no estás familiarizado con los guardias de ruta de Angular en general, y te gustaría saber más, la documentación está aquí. Entrar en detalles sobre los guardias está fuera del alcance de este post, pero debes saber que hay otros guardias además de resolve
disponibles, que es por lo que he sacado el término.
Código con el guardia Resolve añadido
Paso 3: Obtener los datos resueltos de la ruta activada del componente.
Ahora que tenemos una clase resolver y la hemos añadido a nuestra ruta, los datos están siendo pre-fetcheados. El paso final es acceder a los datos pre-fetcheados en nuestro NewsComponent
ubicado en app/news.component.ts
. Navega hasta ese archivo y añade el siguiente módulo ES6:
import { ActivatedRoute } from '@angular/router';
luego proporciona ActivatedRoute
en el constructor del componente News añadiendo el siguiente código en la parte superior de la definición de la clase NewsComponent
:
constructor(private route: ActivatedRoute) {}
Como sabes, o habrás adivinado, ActivatedRoute
nos permite acceder a la información de la ruta que está activa en ese momento, como la url de la ruta, los parámetros de la consulta, etc. Lo que nos interesa aquí son los datos de las noticias que se cargan en el ActivatedRoute
desde la guardia de resolución. Para obtener los datos de las noticias resueltas de la ruta, añada una propiedad para mantener los datos de las noticias:
public news: any;
y luego obtenga los datos de la ruta activada cuando el NewsComponent
se inicialice:
ngOnInit(): void { this.news = this.route.snapshot.data; }
Eso es prácticamente todo. Cambie la plantilla del componente de noticias para mostrar las noticias de la ruta resuelta eliminando los contenidos actuales:
<div>This is just a placeholder for now. News will go here. </div>
y sustituyéndolos por:
Ahora debería ser recibido con las últimas noticias, «El cielo es azul», cuando haga clic en la pestaña Noticias.
Código terminado
Notas &Consejos
– Las resoluciones pueden causar llamadas redundantes a la API: Una resolución obtiene datos cada vez que se carga un componente. Esto suele provocar llamadas innecesarias a la API que afectan negativamente al rendimiento. Si su resolución obtiene datos que no cambian con frecuencia, considere la posibilidad de escribir los datos devueltos por la resolución a una propiedad en la clase de resolución y simplemente devolver esa propiedad si ya ha sido establecida. Por ejemplo, en nuestro ejemplo, añadiríamos una propiedad news
inicialmente indefinida como public news: any = undefined;
en nuestro resolvedor de noticias. A continuación, en el método resolve()
, comprobamos si la propiedad news
ya está establecida y devolvemos su valor sin hacer una llamada a la API si lo está, es decir:
resolve(): Observable<any> { if (this.news) { return this.getSavedNews(); } else { return this.getNewsFromApi() } }
Ejemplo de código completo a continuación. Si la sintaxis observable le parece un poco inusual, es porque estoy usando RxJS 6
. Hay un buen tutorial sobre los cambios introducidos recientemente en RxJS
aquí si necesitas un repaso.
Devuelve los datos guardados, si ya se han recuperado de la API
Naturalmente, podrías ir más allá y establecer un período de tiempo durante el cual los datos son válidos, no sólo guardando los datos, sino añadiendo otra propiedad timestamp y haciendo una llamada a la API si los datos son más antiguos que x.
– Al igual que en AngularJS, puedes utilizar múltiples resoluciones en la misma ruta. Las llamadas de resolución se realizan en paralelo y el componente se cargará sólo después de que todas las llamadas devuelvan datos. Para utilizar múltiples resoluciones, simplemente añádelas a la ruta:
Entonces se puede acceder a los datos adicionales de la resolución desde la instantánea de la ruta como si se tratara de una única resolución:
ngOnInit(): void { this.news = this.route.snapshot.data; this.alternativeNews = this.route.snapshot.data; }
Código con múltiples resoluciones
– La resolución tiene acceso a los parámetros de la ruta. Digamos que tienes un componente que muestra una lista de títulos de noticias. Cuando se hace clic en una noticia, se abre otro componente que muestra el artículo completo. Antes de cargar ese componente, necesitamos pre-obtener el contenido de la noticia en cuyo título se ha hecho clic – esto se puede hacer en el método resolver. La clase resolver tiene acceso al ActivatedRoute
, por lo que podemos obtener el id de la noticia sobre la que se ha hecho clic:
resolve(route: ActivatedRouteSnapshot) { let id: any = route.params); return this.getNewsStory(id); }
Esto se explica por sí mismo. Para un ejemplo, mira el nuevo archivo src/news-story-resolver.service.ts
en el enlace de abajo. Los enlaces al nuevo componente se añadieron en la pestaña de noticias (news.component.ts
).
Código con resolución parametrizada