Parámetros en las rutas Angular

  • Por
Cómo trabajar con parámetros en las rutas, configurar el sistema de routing para crear rutas que envían parámetros y recibir los parámetros en los componentes.

En los pasados artículos hemos podido aprender a trabajar con el sistema de routing de Angular, pero existe una situación muy típica que no hemos tenido tiempo de conocer todavía y que vamos a abordar ahora. Se trata de generar rutas que aceptan parámetros.

En principio puede ser una tarea sencilla, pero hay varios asuntos delicados que se deben conocer para hacer las cosas bien. No se trata solamente de crear enlaces que envían parámetros, sino aprender a recibir esos parámetros. Además, cuando los parámetros cambian, saber detectar esos cambios de manera asíncrona. Vayamos poco a poco para que sea fácil de entender.

Declarar rutas que utilizan parámetros

El primer paso será declarar en nuestro listado de rutas la posibilidad de recibir parámetros. Para ello usaremos una notación especial que te resultará familiar si has usado otros sistema de routing en el pasado. Podemos verla a continuación.

{ path: 'camino/:parametro', component: ParametroComponent }
Nota: ten en cuenta que esta declaración de ruta es un elemento del array Routes. Lee el artículo de introducción al sistema de routing para más información. Luego en este artículo encontrarás un ejemplo completo de declaración de todas las rutas en el array.

En resumen, al crear el camino, estamos indicando varios segmentos, separados por "/". En alguno de esos segmentos estamos colocando el carácter ":" delante, con el que se indica que eso no es una cadena estática, sino un parámetro.

La diferencia entre "camino" y ":parametro" es que la ruta comenzará siempre por "camino", la cadena literal. Sin embargo, donde colocamos ":parametro" quiere decir que nos acepta cualquier texto, es decir, no es una cadena literal específica, sino cualquier cadena que se pueda recibir.

Por tanto, esa ruta parametrizada será siempre servida por el componente "ParametroComponent", y aceptará rutas diversas, como podría ser:

  • example.com/camino/1
  • example.com/camino/2
  • example.com/camino/otro
  • example.com/camino/cualquier_cosa

Podemos indicar cualquier número de parámetros en una declaración de ruta. En ese caso usamos la notación de los ":" por cada parámetro que se quiera definir. Por ejemplo podríamos tener algo como esto:

{ path: 'coches/:marca/:modelo', component: CochesComponent }

En la ruta declarada tenemos dos parámetros "marca" y "modelo". Nos aceptará rutas como las siguientes:

  • example.com/coches/seat/ibiza
  • example.com/coches/fiat/panda
  • example.com/coches/audi/a8

El número de parámetros no acepta ni más ni menos de los indicados

Pero, ojo, para que la ruta concuerde con la anterior declaración, el número de parámetros definidos en las rutas debe ser exacto. Es decir, la declaración anterior no aceptará rutas como estas:

  • example.com/coches
  • example.com/coches/ford

Si queremos atender a estas rutas, deberíamos crear una nueva declaración en el array Routes, para la ruta que acepte solo el segmento "coches" y el segmento con un único parámetro "coches/:marca".

Generar enlaces a rutas enviando parámetros

El siguiente paso que tendremos que hacer es generar enlaces a esa ruta, enviando parámetros. Esta parte no tiene ningún secreto en especial, simplemente tendremos la ruta en la que enviaremos los valores que se deseen como parámetros.

<a routerLink="/coches/seat/ibiza">Seat Ibiza</a>

Como puedes ver, esa ruta casa con nuestra declaración, enviando una marca y un modelo de coche determinado.

Además, podemos crear las rutas mediante la directiva "routerLink" con una notación de array, lo que tendrá mucho sentido más adelante, cuando partes de esa ruta sean dinámicas y correspondan con una propiedad del componente.

<a [routerLink]="['/coches', 'ford', 'focus']">Ford Focus</a>

En este caso, por pasar un array como valor a la directiva routerLink, estamos obligados a colocar el nombre de la directiva entre corchetes, ya que el valor asignado no es un simple literal de cadena. Como puedes ver, cada segmento de la ruta corresponde con una casilla del array.

En este ejemplo quizás no tenga mucho sentido usar esa notación de array, pero como decía, si la marca o modelo fueran propiedades del componente, sí que sería muy útil.

<a [routerLink]="['/coches', miMarca, miModelo]">Ford Focus</a>

En este ejemplo "miMarca" y "miModelo" se supone serían variables dinámicas, por lo tanto este enlace nos llevará a un sitio u otro dependiendo de esas propiedades del componente.

Recibir valores de los parámetros

Ahora viene la parte más interesante, en la que vamos a recuperar los parámetros enviados mediante el sistema de rutas. Esta parte se puede complicar un poco, como luego veremos. Para hacerlo más sencillo aplicaremos distintas alternativas, con dificultad creciente.

En cualquiera de las alternativas, para recibir los valores de los parámetros en el componente al que nos dirige la ruta tenemos que usar un objeto del sistema de routing llamado "ActivatedRoute". Este objeto nos ofrece diversos detalles sobre la ruta actual, entre ellos los parámetros que contiene.

Para usar este objeto tenemos que importarlo primero. Lo haremos en el componente donde vas a recibir los parámetros, claro.

import { ActivatedRoute, Params } from '@angular/router';

Luego tendrás que inyectar ese objeto en el constructor.

constructor(private rutaActiva: ActivatedRoute) { }

Una vez inyectado ya lo puedes usar dentro del componente, para obtener los datos que necesitas. Dada la declaración anterior, la propiedad del componente "rutaActiva" será la que contenga el objeto que vamos a necesitar para recuperar las rutas.

Recibir valores de parámetros con el snapshot

La manera más sencilla de comenzar es usar el snapshot de los valores de los parámetros. el problema de esta alternativa es que no siempre funcionará, pero vamos a verla primero porque nos resultará más fácil de entender.

Una vez inyectada la ruta activa mediante el objeto de tipo ActivatedRoute, podemos acceder a los parámetros en el método ngOnInit().

El objeto AtivatedRoute lo hemos recibido por medio de una propiedad del componente llamada rutaActiva. Para acceder a un snapshot de los parámetros en un instante dado usamos este código:

this.rutaActiva.snapshot.params

El snapshot te da los parámetros del componente en el instante que los consultes. El objeto parmas contendrá todas las propiedades según los parámetros recibidos. Quiere decir que, el modelo lo recuperaremos con "this.rutaActiva.snapshot.params.modelo" y la marca con "this.rutaActiva.snapshot.params.marca".

Por tanto, este podría ser el código de nuestro componente "CochesComponent" hasta ahora:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-coches',
  templateUrl: './coches.component.html',
  styleUrls: ['./coches.component.css']
})
export class CochesComponent implements OnInit {
  coche: {marca: string, modelo: string};

  constructor(private rutaActiva: ActivatedRoute) { }

  ngOnInit() {
    this.coche = {
      marca: this.rutaActiva.snapshot.params.marca,
      modelo: this.rutaActiva.snapshot.params.modelo
    };
  }

}

Fíjate en los siguientes puntos:

  • El import de ActivatedRoute
  • La declaración de la propiedad coche del componente. Esta declaración ya viene con un tipo declarado, creado por medio de un objeto "inline". Esto es como si hubiésemos hecho una interfaz y hubiésemos dicho que el objeto "coche" es de esa interfaz. Pero en lugar de ello estoy declarando la interfaz en la misma declaración de la propiedad.
Nota: Quizás esa declaración de una interfaz "en línea" es un poco compleja de ver y puedas pensar que es innecesario. De hecho, no se necesita realmente, sólo lo he hecho para crear un tipo concreto a la propiedad "coche", pues si no lo hago el editor se queja y me muestra errores en tiempo de desarrollo que me molestan a la hora de prorgramar. Lo ideal sería definir el modelo de datos, para el tipo coche en un archivo aparte e importarlo en el componente.
  • La inyección del ActivatedRoute en el constructor
  • La inicialización de la propiedad "coche" dentro de ngOnInit()
  • En esa inicialización es donde accedemos al snapshot y colocamos cada uno de los parámetros recibidos como propiedades del objeto coche.

Ahora, en nuestro template, podremos usar la propiedad coche, inicializada gracias al ngOnInit() y el acceso a los parámetros recibidos.

<h1>
  Coches en venta
</h1>
<p>
  Este es un coche marca: {{coche.marca}}
</p> 
<p>
  Modelo: {{coche.modelo}}
</p>

Te preguntarás ¿Por qué esta aproximación de la recepción de parámetros en Angular podría no funcionar? El caso es que nuestro componente podría no ser consciente de que los parámetros han cambiado en un momento dado.

Tal como hemos dejado el ejemplo hasta el momento, si has ido programando en tu propio proyecto con estas indicaciones, podrás encontrar el problema, si cambias de una ruta a otra dentro del mismo componente. La primera te recoge bien los parámetros, pero si ya estás en "CochesComponent" y pulsas un enlace a la misma ruta pero con valores de parámetros distintos, observarás que no se refrescan los valores en el template.

El problema es que la inicialización de los parámetros la hemos colocado en el ngOnInit() y si el componente ya pasó su etapa de inicialización, no se vuelven a recibir los parámetros con el snapshot. Por eso, si cambian los parámetros estando ya en el componente "CochesComponent", no se reciben los nuevos valores.

Recibir valores de parámetros con un observable

Los observables de Angular y RxJS nos vienen a solucionar esta situación. Como sabes, los observables nos permiten suscribirnos a eventos que se recibirán de manera asíncrona, mediante programación reactiva y manteniendo un alto rendimiento.

Nota: si no conoces los observables tenemos una introducción que te los explica de manera bastante detallada. Lee el artículo Introducción a observables en Angular.

Básicamente, en el objeto de tipo AtivatedRoute tenemos una propiedad llamada "params" que es un observable y que nos sirve para suscribirnos a cambios en los parámetros enviados al componente.

Las suscripciones a los cambios en los observadores se realizan mediante el método suscribe(). Ese método recibe varios parámetros y el que nos interesa de momento es solo el primero, que consiste en una función callback que se ejecutará con cada cambio de aquello que se está observando.

En resumen, "this.rutaActiva.params" es el observable y "this.rutaActiva.params.subscribe()" es el método para suscribirnos a los cambios. A suscribe le enviaremos una función callback, la cual recibirá el nuevo valor, y se ejecutará cada vez que cambie.

La función callback recibirá un objeto de clase Params. Ese objeto también lo vas a tener que importar desde "@angular/route".

import { ActivatedRoute, Params } from '@angular/router';

El código de la suscripción es el siguiente.

this.rutaActiva.params.subscribe(
    (params: Params) => {
      this.coche.modelo = params.modelo;
      this.coche.marca = params.marca;
    }
  );
Nota: usamos una función flecha para poder seguir usando la variable "this". Puedes aprender sobre ésto en el artículo Arrow Functions de ES6.

Veamos ahora cómo quedaría el código de nuestro componente. En este caso el se nos complica algo más, pero si ya estás familiarizado con los observables no te resultará en ningún problema.

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';

@Component({
  selector: 'app-coches',
  templateUrl: './coches.component.html',
  styleUrls: ['./coches.component.css']
})
export class CochesComponent implements OnInit {
  coche: {marca: string, modelo: string};

  constructor(private rutaActiva: ActivatedRoute) { }

  ngOnInit() {
    this.coche = {
      marca: this.rutaActiva.snapshot.params.marca,
      modelo: this.rutaActiva.snapshot.params.modelo
    };
    this.rutaActiva.params.subscribe(
      (params: Params) => {
        this.coche.modelo = params.modelo;
        this.coche.marca = params.marca;
      }
    );
  }

}

Con esta modificación podemos estar seguros que, si accedemos a nuevas URL que simplemente envían nuevos parámetros al componente, Angular actualizará el template, mostrando los nuevos datos recibidos.

si te sales del componente para ir a otras rutas se debería cancelar la suscripción en el método ngOnDestroy(). Sin embargo, esto es algo que Angular ya realiza por ti mismo en el caso de las suscripciones a cambios en parámetros de las rutas, por lo que para este ejemplo en concreto no te necesitas preocupar.

Conclusión

Con esto hemos terminado nuestras explicaciones sobre parámetros en las rutas, enviados a componentes de Angular. Hemos visto cómo declarar rutas que reciben parámetros, cómo generar enlaces que hacen envío de valores para esos parámetros y cómo recibirlos mediante dos aproximaciones distintas.

La aproximación de los snapshots puede ser suficiente si estás seguro que los datos de los parámetros no van a cambiar durante la vida del componente. La aproximación con observables es más fiable, porque funcionará siempre, cambien o no cambien los valores de los parámetros enviados. Por tanto, es una buena práctica usar observables y será absolutamente necesario si los parámetros en la ruta pueden cambiar durante la vida de un componente.