Parámetros de entrada
Cuando queremos ir a la página de detalle de un producto cuyo componente hemos creado antes (product-detail), debemos indicar qué producto vamos a ver. La ruta definida para esta vista es 'products/:id'.
Los componentes de una ruta que comienzan con ':' son parámetros de la ruta y son valores variables. Al cargar la ruta de detalle de un producto, se pondrá la id del producto asociado a ese parámetro, por ejemplo products/4 (donde 4 es el id del producto).
Veamos cómo llamar a esta ruta a partir de un enlace asociado a la descripción de un producto:
Antes de ver como inyectar la id del producto en product-item, vamos a crear el método en el servicio ProductsService para obtener un producto del servidor a partir de la id.
Inyectar parámetros de la ruta
Las últimas versiones de Angular permiten que el componente reciba los parámetros de la ruta como parámetros de entrada (input). Además, podemos pasarle la opción transform que recibe una función que transforma el dato antes de devolverlo. En este caso, la función numberAttribute (@angular/core), nos lo devuelve como number.
Para usar inputs para obtener parámetros de ruta, debemos añadir la llamada a la función withComponentInputBinding como parámetro a la función provideRouter en el archivo app.config.ts.
Antes de existir esta opción se debía utilizar el servicio ActivatedRoute y acceder a los parámetros de la ruta desde ahí. Esta opción está disponible desde Angular 2.0.
Podríamos aprovechar también la interoperabilidad entre observables y signals de la versión 18 de Angular:
A partir de Angular 19 también está la opción de utilizar la API Resource con funciones como rxResource para crear un recurso a partir de un observable que se actualizará cada vez que cambie una señal vinculada (en este caso la id), y del que podríamos extraer información como el estado (status), si está cargando un nuevo producto , etc. para darle un feedback más preciso al usuario.
Los recursos creados con resource o rxResource son de lectura/escritura. Es decir, cuando cambie la id automáticamente se recargaría el producto a partir del servidor, pero también podríamos modificar y sustituir el producto en local → productResource.set(...).
Mostrando datos asíncronos
Vamos a crear una plantilla para mostrar los datos del producto. La cuestión es que el producto, hasta que el servidor nos devuelve los datos, está con valor undefined. Si no comprobamos eso, Angular nos dará un error cuando trate de acceder a las propiedades del producto en la plantilla la primera vez. Después, una vez recibe el producto y lo asigna, lo renderiza correctamente.
Para evitar eso podríamos utilizar product? en la plantilla, y mientras no haya cargado el producto, los campos estarán vacíos. Sin embargo, vamos a englobar todo dentro de una estructura @if, para poder mostrar un mensaje de carga alternativo mientras esperamos a los datos.
Al incorporar en la plantilla el componente star-rating, deberíamos poner el método para cambiar la puntuación en el producto también en product-detail, el mismo que en product-item. Si compartieran más lógica, podríamos considerar reutilizar el componente product-item pasándole un booleano por ejemplo, que indicara si queremos el HTML con la estructura de card o de fila.
Navegación desde código
Aunque este apartado currespondería más a la sección anterior (Enrutamiento básico), es un buen momento para introducir un ejemplo en nuestra aplicación de productos.
Para navegar a otra página desde código, debemos inyectar en el componente el servicio Router de Angular. Con el método navigate, le podemos pasar un array con los componentes de la ruta donde queremos ir, igual que se los pasamos a la directiva routerLink. También está el método navigateByUrl que recibe un string con la ruta completa.
Vamos a crear un método para volver a la página del listado de productos desde el detalle.
Precarga de datos: Resolver
¿Y si queremos obtener los datos (por ejemplo, un producto) antes de cargar el componente de una ruta? (como la página de detalles del producto). En lugar de obtener el producto a partir del id en ProductDetailComponent, lo obtendremos antes de cargar la ruta utilizando un servicio de tipo Resolver. De esta manera, sabremos que cuando se cargue el componente, el producto estará disponible de inmediato.
La función de tipo Resolver (también se puede crear como una clase) puede devolver los datos directamente, en una promesa o en un observable (el router de Angular se suscribirá a él automáticamente). También usaremos la función catchError del observable para detectar errores. En caso de error al obtener el producto, la función catchError devolverá un observable vacío (EMPTY) y redireccionaremos a la página de productos.
ng g resolver resolvers/product
Posteriomente incluimos el resolver en la ruta a la página de detalle un producto. La propiedad resolve de la ruta será un objeto cuyas propiedades son los datos a precargar antes de ir a la ruta y el valor, será la función de tipo resolver que los obtiene.
En el componente recibimos este dato en un parámetro de entrada (input) con el mismo nombre (ya no necesitaremos la id de la ruta).
Aunque se simplifica la lógica en el componente al precargar los datos con un resolver, no siempre es la mejor opción.
Ventajas de usar resolver
- La responsabilidad de obtener el dato ya no está en el componente de la página.
- Al cargar la página de destino, el dato está siempre disponible, por lo que no hay que comprobar.
- Se puede hacer una redirección a otra página (ej: página de error 404) si detectamos que el dato no existe antes de cargar el componente de la ruta
Desventajas de usar resolver
- Importante: Si la conexión no es rápida, el usuario verá como el enlace parece que no funciona, ya que no cargará nada durante un rato, lo que puede provocar desesperación y que haga click varias veces en el mismo
- A partir del punto de arriba habría que darle un feedback al usuario (animación, mensaje, etc.) de que tiene que esperar a que la página de destino se cargue. Es más fácil darle ese feedback en la página de destino.