Routing en Angular: Guía completa: Parte 5

Seguimos aprendiendo sobre routing desde cero. En esta 5ª parte nos adentramos en el mundo de las rutas anidadas (en inglés, nested routes). Si te la perdiste, aquí tienes la 4ª parte.

Cómo configurar rutas hijas (nested routes)

Sería más útil si mostrásemos un servidor o un usuario a la derecha de la lista de servidores y usuarios, en lugar de abrir una página nueva para cada uno, perdiendo la lista de la vista. Algo así:

Lista de servidores

servidor1

servidor2

servidor 3

servidor1

Este es el servidor1, aquí hay información súper útil e interesante sobre él.

Podemos conseguir esto configurando rutas hijas (en inglés, nested routes child routes). 👶🏽

Además, si te fijas, nuestro appRoutes del AppModule tiene varios elementos repetidos, demasiados servers y users por ahí. Esto tiene pinta de que podría simplificarse. 

Empezamos con servers, quedándonos con la ruta principal (servers) y conviertiendo los dos posteriores en child routes de la principal

1. En servers, añadimos otra propiedad llamada childrenque será un array de rutas hijas. 

2. Cogemos las dos rutas posteriores (servers/:id servers/:id/edit) y las colocamos dentro del array.

3. Eliminamos la parte de servers porque la ruta principal ya nos da esa información.

  { path: 'servers', component: ServersComponent, children: [
    { path: ':id/edit', component: EditServerComponent },
    { path: ':id', component: ServerComponent }
  ] 
  },

De esta manera conseguimos agrupar todas las rutas relacionadas con servers, lo que nos deja un código más limpio y ordenado

El problema viene en que ahora Angular no sabe dónde cargar el ServerComponent. Por eso, si guardas y vas al navegador, verás que la app no responde cuando haces clic en algún servidor de la lista. Puede que la consola te de un error indicándote que no puede encontrar el router-outlet para mostrarlo en tu navegador. En mi caso no me dice nada. Vamos, que me busque la vida 😒. Menos mal que Max explica perfectamente lo que está pasando en su curso de Angular

La razón por la que no puede encontrar el router-outlet es porque el único router-outlet de nuestra app está en el app.component.html, pero ese elemento únicamente mostrará las rutas de un nivel superior, no las rutas hijas. He ahí el quid de la cuestión.

Es decir, que las child routes necesitan un outlet independiente, porque cuando una ruta se convierte en "ruta madre", necesita ofrecer a sus rutas hijas un punto al que engancharse, y eso es el outlet.

4. Para eso, vamos al servers.component.html y reemplazamos la parte donde cargamos el EditServerComponent y el ServerComponent por el router-outlet, tan sencillo como eso.

5. Eliminamos también el button.

  <div class="col-sm-4">
    <router-outlet></router-outlet>
  </div>

¡Y listo!

nesterRoutes

Hagamos lo mismo para las rutas de los users.

6. En el AppModule, convertimos las rutas normales en nested routes, aunque sólo hay una que convertir en este caso.

  { path: 'users', component: UsersComponent, children: [
    { path: ':id/:name', component: UserComponent },
  ] 
  },

7. En el users.component.html, sustituimos el custom element <app-user> por el router-outlet.

  <div class="col-sm-4">
    <router-outlet></router-outlet>
  </div>

¡Y ya está!

Cómo usar los query params para navegar hasta la pantalla de edición de un servidor

Ya que no tenemos forma de acceder al EditServerComponent salvo si ponemos manualmente en nuestro navegador una URL tipo http://localhost:4200/servers/2/editvamos a crear una manera más fácil de acceder. 

1. Añadimos un botón en el server.component.html para editar un servidor.

2. Le damos un poco de estilo con clases de Bootstrap e incluímos un click listener vinculado a un método al que llamamos onEditpor ejemplo.

<button class="btn btn-primary" (click)="onEdit()">Edit server</button>

3. En el server.component.ts, configuramos el onEdit para poder navegar hasta el EditServerComponent.

4. Inyectamos el router en el constructor y lo importamos.

5. Llamamos al router desde el método y aplicamos el método navigate, que espera un array. Únicamente necesitamos agregar el fragmento edit a la ruta actual. Para eso, le especificamos con la propiedad relativeTo que el edit debe ser relativo a la ruta en la que nos encontremos, que siempre será /servers porque desde ahí es el único sitio desde donde podemos acceder al EditServerComponent.

  onEdit() {
    this.router.navigate(['edit'], {relativeTo: this.route});
  }

Si guardas y pinchas en el botón Edit server, verás que se carga la página del componente para editar el servidor, ¡chachi! 🤗


Yendo un paso más allá, vamos a configurar la posibilidad de que un usuario pueda tener derecho a editar un servidor o no. El problema con esto es que, al hacer clic en el botón Edit server, perdemos los query params del navegador.



Esto es algo que tenemos que arreglar, ya que necesitamos esa información para determinar quién tiene derecho a editar servidores y quién no.

Recuerda que en el servers.component.html tenemos nuestra lista de servidores con el parámetro allowEdit.

6. En los queryParams del servers.component.html, hacemos la propiedad allowEdit más dinámica, eliminando la posibilidad de que siempre sea true (1). Vamos a decidir si admitimos o no editar un servidor dependiendo del ID del servidor, por ejemplo. Vamos a darle la posibilidad de editar al servidor con ID 3, usando un ternary operator. 

true = 1
false = 0

        [routerLink]="['/servers', server.id]" [queryParams]="{allowEdit: server.id === 3 ? '1' : '0'}">

Desde el edit-server.component.ts rescataremos los query params. En el método subscribe de los query params es donde determinaremos si estamos autorizados a editar el servidor o no.

7. Añadimos una nueva propiedad llamada allowEdit y le damos el valor de false.

8. Obtenemos los queryParams, que son de tipo Params, y en el cuerpo de la función asignamos la propiedad allowEdit al allowEdit del argumento (queryParams). Con un ternary operator, comprobamos si equivale a 1, en cuyo caso será true. En caso contrario, será false.

  allowEdit = false;

  constructor(private serversService: ServersService,
              private route: ActivatedRoute) { }

  ngOnInit() {
    this.route.queryParams.subscribe(
      (queryParams: Params) => {
        this.allowEdit = queryParams['allowEdit'] === '1' ? true : false;
      }
    );

9. En el edit-server.component.html, añadimos un mensaje que diga que no estamos autorizados a editar en caso de que la propiedad allowEdit sea false, cosa que hacemos con un sencillo ngIf.

10. Englobamos el resto de la template en un div que sólo se mostrará si la propiedad allowEdit es true.


<div *ngIf="!allowEdit" class="alert alert-warning" role="alert">
  You are not allowed to edit!
</div>

<div *ngIf="allowEdit">
  <div class="form-group">
    <label for="name">Server Name</label>
    <input type="text" id="name" class="form-control" [(ngModel)]="serverName">
  </div>
  <div class="form-group">
    <label for="status">Server Status</label>
    <select id="status" class="form-control" [(ngModel)]="serverStatus">
      <option value="online">Online</option>
      <option value="offline">Offline</option>
    </select>
  </div>
  <button class="btn btn-primary" (click)="onUpdateServer()">Update Server</button>
</div>

A pesar de estos cambios, verás que nuestros query params siguen desapareciendo, y eso hace que no tengamos posibilidad de editar ningún servidor, porque además, hemos perdido el acceso a la pantalla donde antes se mostraba un servidor para editar 😬. Vamos a arreglar eso en el siguiente punto.

Cómo preservar parámetros

Nuestro objetivo es preservar la información de los query params (en la URL) cuando hacemos clic en uno de los servidores de la lista de Servers (en la pestaña Servers). Es decir, cuando naveguemos al componente EditServer.

1. En el server.component.ts, le pasamos otra propiedad al objeto relativeTo del router, llamada 
queryParamsHandling.

Esta propiedad espera un string, cuyos valores pueden ser:

  • merge ➡️ fusionará cualquier query param existente con los nuevos.
  • preserve ➡️ reemplaza el comportamiento por defecto, que es eliminar los query params, justo lo que nos sucede ahora. Y mantiene los query params existentes. 

El valor preserve es el que necesitamos.

    this.router.navigate(['edit'], {relativeTo: this.route, queryParamsHandling: 'preserve'});

Con estos cambios, ya volvemos a ver la sección para editar un servidor, aunque sólo en el Devserver, porque es el que tiene el ID 3, y es el único que hemos configurado para que tenga derechos de edición.


Para los otros dos, debería salirte el mensaje amarillo de denegación de acceso 🙅🏽. Pero aún nos queda mucho que configurar. ¡Sigamos adelante!

Cómo redireccionar

En el último punto de este post vamos a aprender a redireccionar URLs, cosa que aprendí en este curso de Angular, entre otras muchas cosas útiles.

Como podrás comprobar, si escribes cualquier cosa en la URL después de http://localhost:4200/ , por ejemplo http://localhost:4200/random, la consola te devuelve un error, porque esa ruta no existe.

core.js:4002 ERROR Error: Uncaught (in promise): Error: Cannot match any routes. URL Segment: 'random' 

Hasta aquí todo lógico. Pero tendremos que solucionar eso, porque existe la posibilidad de que un usuario escriba una ruta que no existe, ya sea por un error suyo o porque nuestra app no logre encontrarla por algún fallo interno. 

Es decir, vamos a hacer la típica y necesaria página de "404, error! página no encontrada" y a re-dirigir ahí al usuario que escriba una ruta inexistente.

1. Generamos un componente con la CLI dentro de la carpeta app, al que llamamos PageNotFound, por ejemplo.

ng g c page-not-found --skipTests

2. En el page-not-found.component.html, escribimos un pequeño mensaje para informar al usuario de que esa página que buscaba no existe.

<div class="alert alert-info" role="alert">
  Ooops! page not found ðŸ™
</div>

3. En el app.module.ts, añadimos otro path vinculado a la URL not-found, que cargará el componente PageNotFound.

Y aquí viene lo interesante. Añadimos otro path con la URL **, que identificará cualquier caracter que no esté registrado como una de nuestras rutas. Es como una ruta comodín (en inglés, wildcard). 🃏

Esa wildcard necesita cargar el componente PageNotFound, pero en lugar de indicarle un componente a cargar, usamos la propiedad redirectTo, y redirigimos cualquier ruta no registrada a la URL not-found, que cargará la página PageNotFound que hemos creado.

Para hacer la ruta absoluta, le añadimos la barra /.

  { path: 'not-found', component: PageNotFoundComponent},
  { path: '**', redirectTo: '/not-found'}
El orden es súper importante, debiendo ser la "ruta comodín" la última de todas en el array de routes, porque las rutas son leídas de arriba hacia abajo.

Si ahora guardas y pruebas a escribir cualquier cosa después de http://localhost:4200/  , verás que se te redirige a la página: 


No es perfecto, porque no cubrimos todos los casos, ya que sólo funciona si escribimos una ruta inexistente después de http://localhost:4200/ , no después de http://localhost:4200/servers/, por ejemplo, pero espero que se haya entendido el concepto de redirección.

THE END!

¡Y con esto terminamos esta 5ª parte de la Routing saga! Espero que hayas aprendido algo nuevo 😊.  Si te queda alguna duda, ¡nos vemos en los comentarios! Y si quieres seguir aprendiendo, aquí tienes la parte #6 (disponible próximamente).

Sobre la autora de este post

Soy Rocío, una abogada reconvertida en programadora. Soy una apasionada de aprender cosas nuevas y ferviente defensora de que la única manera de ser feliz es alcanzando un equilibrio entre lo que te encanta hacer y lo que te saque de pobre. Mi historia completa, aquí. 

Si quieres ayudar a hacer este blog sostenible, puedes invitarme a un café digital ツ
¡Gracias!¡Gracias!

Más recursos de aprendizaje

En mi experiencia, la manera más eficaz para aprender Angular es combinando varias vías de aprendizaje. Uno de mis métodos favoritos son los vídeo-cursos y mi plataforma predilecta para eso es Udemy. He hecho varios cursos pero sólo recomiendo aquellos que verdaderamente me han sido útiles. Aquí van:

angular the complete guide - curso Max S.

  Max Schwarzmüller

curso angular fernando herrera

   Fernando Herrera

Si necesitas apoyo en forma de libro, puede que éstos te sirvan de ayuda:

libro 1 angular
libro 2 angular

La programación es un mundo que evoluciona a una velocidad de vértigo. Los autores de estos libros lo saben, por eso suelen encargarse de actualizar su contenido regularmente. Asegúrate de que así sea antes de adquirirlos 😌.

Participo en el programa de afiliados de Udemy y Amazon, lo que significa que, si compras alguno de estos cursos y/o libros, yo me llevaré una pequeña comisión y a ti no costará nada extra. Vamos, lo que se dice un win-win ðŸ˜Š.

Otros artículos que pueden interesarte

Guía de iniciación al data binding en Angular
¿Qué es el databinding?El databinding es la forma que tiene Angular para permitirnos mostrar contenido dinámico en lugar de estático (en inglés, hardcoded). Podríamos[...]
Cómo aprendí a programar cuando estaba «programada» para ser de letras
[tcb-script src="https://player.vimeo.com/api/player.js"][/tcb-script]A nadie le gusta su trabajo. Eso es lo que me decía a mí misma cuando conseguí mi primer[...]
Cómo construir un pop-up con vanilla JavaScript
¿Qué vamos a construir? Vamos a construir una ventana emergente, popup, overlay, dialog o como quieras llamarla, pero esos 3 nombres son[...]
Si crees que este post puede serle útil a alguien, por favor, ¡compártelo!:

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Esta web utiliza cookies para asegurar que se da la mejor experiencia al usuario. Si continúas utilizando este sitio se asume que estás de acuerdo. más información

Los ajustes de cookies en esta web están configurados para «permitir las cookies» y ofrecerte la mejor experiencia de navegación posible. Si sigues usando esta web sin cambiar tus ajustes de cookies o haces clic en «Aceptar», estarás dando tu consentimiento a esto.

Cerrar