¿Qué aprenderá?

En este tutorial, podrá seguir los pasos para implementar el manejo de rutas para navegar a través de los componentes de una aplicación Angular.

¿Qué construirá?

La Figura 1 muestra lo que lograremos al finalizar el tutorial.

Figura 1. Aplicación que se construirá en este tutorial.

¿Qué necesita?

Este tutorial lo hacemos como una extensión al presentado en Desplegar detalle de libro y del tutorial de Manejo de error.

Una ruta define un camino (path) que lleva a un componente en una zona específica de la página (router-outlet) definida por el desarrollador.

En Angular, la asociación de una ruta con un elemento de interfaz, se define utilizando el atributo routerLink. Este atributo reemplaza al atributo href de un enlace estándar en HTML.

En este tutorial vamos a crear las siguientes rutas y su navegación:

Rutas para listar

Un menú principal en la aplicación que lleva, a través de las rutas, al componente listar de Book, Author y Editorial. Las rutas son:

Elemento donde está asociada la ruta con routerLink

Ruta

Componente

Menú Book

‘books/list'

BookListComponent

Menú Author

‘author/list'

AuthorListComponent

Menú Editorial

‘editorial/list'

EditorialListComponent

La aplicación fue diseñada para que la zona de despliegue (router-outlet) esté entre el menú y el footer de la aplicación.

Rutas para navegar a detalles

Cuando desde el despliegue del detalle de un libro, el usuario da clic sobre el nombre del autor, este nombre tiene un routerLink con la ruta ‘author/:id' donde el id corresponde con el identificador del autor seleccionado.

Esta ruta tiene asociado el componente de detalle del autor denominado AuthorDetailComponent.

Igualmente cuando desde el detalle de autor o desde el detalle de editorial, el usuario da clic sobre la imagen de un libro, esta imagen tiene tiene un routerLink con la ruta ‘book/:id' donde el id corresponde con el identificador del libro seleccionado. Esta ruta tiene asociado el componente de detalle del libro denominado BookDetailComponent.

Elemento donde está asociada la ruta con routerLink

Ruta

Componente

Desde la imagen de un libro

‘books/:id'

BookDetailComponent

Desde el nombre de un autor

‘author/:id

AuthorDetailComponent

Primero se deben completar los módulos de autor y editorial.

La Figura 2 muestra el diagrama de clases de los elementos que intervienen y en la parte de abajo, un fragmento del HTML de la vista del componente listar libros, para ver el llamado al componente detalle y el paso del parámetro.

Figura 2. Diagrama de clases.

Autor

AuthorModule con los componentes AuthorListComponent y AuthorDetalleComponent.

El componente de listar los autores despliega lo que se observa en la Figura 3.

Figura 3. Despliegue del componente de listar.

Y el componente de despliegue de un detalle de un autor se detalla en la Figura 4.

Figura 4. Despliegue del detalle.

Editorial

EditorialModule con el componente EditorialListComponent. Dado que una editorial sólo tiene su nombre y la lista de libros que edita, hemos decidido no implementar un componente de detalle dado que el componente de listar las editoriales ya muestra para cada uno de sus libros (ver Figura 5).

Figura 5. Libros por editoriales

Las rutas de una aplicación se pueden definir en un único módulo de rutas, que importa el módulo principal, o se pueden definir en los módulos funcionales. En cada módulo funcional se definen las rutas del módulo.

Esta última forma es la que vamos a desarrollar en este tutorial. Cada módulo define sus propias rutas.

La Figura 6 muestra el módulo principal AppModule y los tres módulos funcionales (BookModule, AuthorModule, EditorialModule). El módulo principal importa los demás módulos.

Figura 6. Módulos de la aplicación.

Hemos definido que cada módulo tenga su definición de rutas. Las rutas se definen en módulos que importan el RouterModule de Angular y que declaran un arreglo de rutas que se agrega a las rutas de la aplicación. La figura muestra la estrategia: Cada módulo funcional importa su propio módulo de rutas (ver Figura 7).

Figura 7. Módulos de rutas en la aplicación.

Hemos definido que la página principal, es decir a la que llega el usuario al iniciar la aplicación sea la galería de libros. Entonces la ruta por defecto tiene asociado el componente de listar libros BookListarComponent. Esta ruta la definimos en el módulo AppRouting que es que módulo que importa el principal AppModule.

Note que el arreglo de Routes se adiciona al módulo RouterModule a través de la función forRoot.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { BookListarComponent } from './book/book-listar/book-listar.component';

const routes: Routes = [
  { path: '', component: BookListarComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRouting {
}

Las rutas del módulo BookModule están definidas en su propio módulo.

BookRoutingModule. Se define una ruta principal, books, que en este ejemplo no tiene asociado ningún componente, y dos rutas hijas: list, para listar los libros y :id para mostrar el detalle del libro identificado con el id. Por ejemplo si el usuario selecciona un libro cuyo id es 100, la ruta es books/100. Cada una de las rutas tiene asociado el componente respectivo: BookListarComponent para books/list y BookDetailComponent para books/:id.

El arreglo de las rutas (Routes) es agregado al módulo de rutas utilizando la función forChild.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { BookListarComponent } from './book-listar/book-listar.component';
import { BookDetailComponent } from './book-detail/book-detail.component';


const routes: Routes = [{
  path: 'books',
  children: [
    {
      path: 'list',
      component: BookListarComponent
    },
    {
      path: ':id',
      component: BookDetailComponent    
    },
  ]
}];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class BookRoutingModule { }

Cada uno de los otros módulos sigue el mismo principio. La Figura 8 muestra el esquema global de las rutas.

La Figura 8. Esquema global de las rutas.

Para asociar las rutas con los elementos de interfaz se utiliza la directiva de Angular, que es un atributo de una etiqueta, routerLink.Una forma de pensar este atributo es como el equivalente a href solo que las rutas son las definidas en la aplicación y que siguen el esquema de la figura anterior.

Cuando el usuario hace clic sobre el elemento que tiene definido el routerLink, el componente de la ruta se desplegará en donde hayamos definido la etiqueta router-outlet

La Figura 9 muestra el elemento de menú de Books : En la parte superior el fragmento de código de definición de la ruta, luego la asociación del routerLink con la palabra Books y el router-outlet.

Figura 9. Menú para Books.

En nuestro ejemplo, solo hay un router-outlet y está definido en la vista del componente principal de la aplicación AppComponent. El siguiente fragmento muestra este código:

...
<!-- Main Router Outlet -->
<div class="container-fluid" id="principal">
  <div class="row">
    <div class="col">
      <div>
        <router-outlet></router-outlet>
      </div>
    </div>
  </div>
</div>

<!-- Footer -->
<footer class="footer" id="footer">
  ...
</footer>

La barra de navegación principal de la aplicación, con los tres elementos Book Author Editorial está en el siguiente fragmento de código.

...
<!-- Menu a recursos principales -->
  <div class="navbar-nav mr-auto mt-0">
    <a id="books-navbar-item" class="nav-item nav-link active" routerLink="/books/list">Books</a>
    <a id="authors-navbar-item" class="nav-item nav-link" routerLink="/authors/list">Authors</a>
    <a id="editorials-navbar-item" class="nav-item nav-link" routerLink="/editorials/list">Editorials</a>
  </div>
...

Vamos a explicar cómo se navega desde la vista del componente BookDetailComponent, que muestra un libro con sus autores, isbn, descripción etc, a la vista del detalle del autor de ese libro.

La Figura 10 muestra la navegación.

Figura 10. Navegación de la aplicación.

Paso 1: Definir el routerLink en el nombre del autor

Asociar rutas a elementos como textos, imágenes, etc, sigue el mismo principio explicado antes.

Es importante notar que se trata de una navegación, entonces se debe pensar cuál es el origen y, desde allí, definir la ruta completa para llegar a la deseada. En el ejemplo del libro, la ruta de origen es:

books/100 (supongamos que 100 es el id del libro "A game of thrones")

Figura 11. Árbol de rutas.

Si nos fijamos en el árbol de rutas (ver Figura 11), para llegar a authors/:id primero debemos subir en el árbol dos niveles. Por eso la ruta asociada con el nombre del autor es:

routerLink="../../authors/{{a.id}}"

Note que el valor del id, se encuentra dentro del atributo id del objeto a de tipo author en la colección de los autores del libro:

<dd *ngFor="let a of bookDetail.authors"><a routerLink="../../authors/{{a.id}}">{{a.name}}</a></dd>

Paso 2: Cargar la información del autor

El componente AuthorDetailComponent actual tiene un parámetro de entrada @Input() que corresponde al autor que se va a desplegar. Sin embargo, en este llamado utilizando el routerLink no enviamos el objeto AuthorDetail sino que enviamos en la ruta el id de dicho autor.

Entonces debemos solicitar el author del id que llega por la ruta al back-end utilizando el API REST. Para esto, debemos:

  1. Agregar un método al servicio para que dado el id del autor lo traiga utilizando http.get. Este método retorna un Observable
  2. Agregar un método en el componente para que se suscriba al Observable del http.get
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';

import { environment } from '../../environments/environment';
import { Author } from './author';
import { AuthorDetail } from './author-detail';


@Injectable({
  providedIn: 'root'
})
export class AuthorService {
  private apiUrl = environment.baseUrl + 'authors';
  constructor(private http: HttpClient) { }

 ...
  getAuthorDetail(authorId): Observable<AuthorDetail> {
    return this.http.get<AuthorDetail>(`${this.apiUrl}/${authorId}`);
  }
}

En el componente, además de la suscripción al Observable, tenemos que:

Identificar si el llamado es por el selector o por la ruta. Si el Input es undefined, significa que el llamado fue utilizando la ruta.

Si es por la ruta entonces tenemos que obtener el id de la ruta. Para esto, utilizamos la librería ActivatedRoute de Angular que ofrece servicios para obtener los valores de la ruta.

import { Component, OnInit, Input } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { BookDetail } from '../bookDetail';
import { BookService } from '../book.service';

@Component({
  selector: 'app-book-detail',
  templateUrl: './book-detail.component.html',
  styleUrls: ['./book-detail.component.css']
})
export class BookDetailComponent implements OnInit {

  @Input() bookDetail: BookDetail;

  constructor(
    private route: ActivatedRoute,
    private router: Router,
    private bookService: BookService
  ) {
  }

  bookId: number;
  getBookDetail(): void {
    this.bookService.getBookDetail(this.bookId)
      .subscribe(bookDetail => {
        this.bookDetail = bookDetail;
      });
  }
  ngOnInit() {
    if (this.bookDetail === undefined) {
      console.log('routerLink');
      this.bookId = +this.route.snapshot.paramMap.get('id');
      this.getBookDetail();

    } else { console.log(this.bookDetail.id); }
  }
}