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.
La Figura 1 muestra lo que lograremos al finalizar el tutorial. En resumen, a la aplicación se le incorpora un menú de navegación y cuando se seleccionan los elementos de ese menú la parte central de la página cambiará.
Figura 1. Aplicación que se construirá en este tutorial. |
Este tutorial lo hacemos como una extensión al presentado en el de despliegue del detalle de un libro.
Una ruta define un camino (path
) que lleva a que un componente se muestre en una zona específica de la página (router-outlet
).
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:
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' |
|
Menú Author | ‘authors/list' |
|
Menú Editorial | ‘editorials/list' |
|
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
Adicionalmente a la navegación entre listas también se tiene la navegación hacia los detalles. Por ejemplo, en el tutorial de detalle, cuando se hace clic en un libro se muestran sus detalles. En la información de ese detalle tenemos la mención a los autores del libro tal como se ve en la siguiente imagen:
Con la navegación a los detalles el usuario podrá dar clic sobre el nombre del autor. En el nombre se incluye un routerLink
con la ruta ‘
author/:id
' donde el id
corresponde al 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' |
|
Desde el nombre de un autor | ‘author/:id |
|
La Figura 2 muestra el diagrama de clases de los elementos que intervienen en la relación entre la lista y los detalles.
Figura 2. Diagrama de clases. |
En el módulo AuthorModule
estarán los componentes AuthorListComponent
y AuthorDetailComponent
. Para ver el contenido de estos componentes revise el repositorio de GitHub con el ejemplo.
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. |
El módulo EditorialModule
tiene 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 de ese 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 el módulo que importa el principal AppModule
. Para esto edite el archivo app-routing.module.ts. 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 { BookListComponent } from './book/book-list/book-list.component';
const routes: Routes = [
{ path: '', component: BookListComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}
Las rutas del módulo BookModule
se definirán en su propio módulo denominado BookRoutingModule
. Las rutas se van a asociar a una ruta principal books y en ejemplo se ven las dos rutas hijas: list
, para listar los libros e :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
.
Este es el código del archivo que hay que crear en el módulo book con nombre book-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { BookListComponent } from './book-list/book-list.component';
import { BookDetailComponent } from './book-detail/book-detail.component';
const routes: Routes = [
{
path: 'list',
component: BookListComponent
},
{
path: ':id',
component: BookDetailComponent
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class BookRoutingModule { }
Ahora, hay que actualizar el archivo de rutas de la aplicación app-routing.module.ts
como se ve a continuación:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { BookListComponent } from './book/book-list/book-list.component';
const routes: Routes = [
{ path: '', component: BookListComponent },
{ path: 'books', loadChildren: () => import('./book/book.module').then(m => m.BookModule) },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}
La sintaxis de loadChildren puede parecer extraña pero lo que hace esta es importar el módulo de Book para obtener sus rutas y cargarlas como hijas de book. Esto hace parte de una función de Angular llamada Lazy Loading.
Luego se debe importar el nuevo módulo de rutas de book en el módulo principal así:
...
imports: [
BrowserModule,
AppRoutingModule,
BookModule,
EditorialModule,
HttpClientModule,
AuthorModule,
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 poder usar la directiva routerLink
en la vista de los componentes se debe importar en los módulos que declaran los componentes el módulo RouterModule
. Este es un ejemplo de cómo quedaría el módulo book.module.ts
con esa nueva importación:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BookListComponent } from './book-list/book-list.component';
import { BookDetailComponent } from './book-detail/book-detail.component';
import { RouterModule } from '@angular/router';
import { BookRoutingModule } from './book-routing.module';
@NgModule({
imports: [
CommonModule,
RouterModule,
BookRoutingModule
],
declarations: [BookListComponent, BookDetailComponent],
exports: [BookListComponent]
})
export class BookModule { }
Asegúrese de hacer esta actualización en los módulos de autor y editorial
Para asociar las rutas con los elementos de interfaz se usa un routerLink
.
Una forma de pensar en un routerLink
es compararlo con el atributo href
, solo que las rutas son las definidas en la aplicación y 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 está el fragmento de código de definición de la ruta y 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 será definido en la vista del componente principal de la aplicación AppComponent
. Cambie el siguiente código:
<app-book-list></app-book-list>
Por el siguiente:
<div class="container-fluid" id="principal">
<div class="row">
<div class="col-md-12">
<div>
<router-outlet></router-outlet>
</div>
</div>
</div>
</div>
<footer class="page-footer">
<div class="footer-copyright text-center py-2">
Sample proyect -
<a routerLink="/books/list" id="colorFooterText2"> Bookly</a>
</div>
</footer>
La barra de navegación principal de la aplicación, con los elementos Book, Author y Editorial está en el siguiente fragmento de código.
...
<!-- Navigation bar -->
<!-- Navigation bar -->
<nav
class="navbar navbar-expand-lg navbar-dark"
style="background-color: #dc3545"
>
<div class="container-fluid">
<a class="navbar-brand" href="#">Bookly</a>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a
class="nav-link active"
aria-current="page"
routerLink="/books/list"
>Books</a
>
</li>
<li class="nav-item">
<a class="nav-link active" routerLink="/authors/list">Authors</a>
</li>
<li class="nav-item">
<a class="nav-link active" routerLink="/editorials/list">Editorials</a>
</li>
</ul>
</div>
</div>
</nav>
...
Nótese en el ejemplo anterior el uso del atributo routerLink
dentro de las etiquetas a
.
Ahora vamos a explicar cómo se navega desde la vista de un componente a otro.
Esta navegación debe ocurrir cuando desde el detalle de un libro se hace click en el nombre de un autor.
Este es el código del archivo que hay que crear en el módulo author con el nombre author-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AuthorListComponent } from './author-list/author-list.component';
import { AuthorDetailComponent } from './author-detail/author-detail.component';
const routes: Routes = [
{
path: 'list',
component: AuthorListComponent
},
{
path: ':id',
component: AuthorDetailComponent
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AuthorRoutingModule { }
Ahora, hay que actualizar el archivo de rutas de la aplicación app-routing.module.ts
como se ve a continuación:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { BookListComponent } from './book/book-list/book-list.component';
const routes: Routes = [
{ path: '', component: BookListComponent },
{ path: 'books', loadChildren: () => import('./book/book.module').then(m => m.BookModule) },
{ path: 'authors', loadChildren: () => import('./author/author.module').then(m => m.AuthorModule) }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}
Luego se debe importar el nuevo módulo de rutas de author en el módulo principal así:
...
imports: [
BrowserModule,
AppRoutingModule,
BookModule,
EditorialModule,
HttpClientModule,
AuthorModule,
BookRoutingModule,
AuthorRoutingModule
],
...
Por último importe RouterModule
y AuthorRoutingModule
al módulo de Author:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AuthorListComponent } from './author-list/author-list.component';
import { AuthorDetailComponent } from './author-detail/author-detail.component';
import { RouterModule } from '@angular/router';
import { AuthorRoutingModule } from './author-routing.module';
@NgModule({
imports: [
CommonModule,
RouterModule,
AuthorRoutingModule
],
declarations: [AuthorListComponent, AuthorDetailComponent]
})
export class AuthorModule { }
routerLink
en el detalle del libro. La asociación de rutas a elementos como textos, imágenes, etc, sigue el mismo principio explicado antes: se incluye el elemento routerLink
con la ruta especificada. Para el caso de los autores esperamos una ruta de la forma /authors/1
(suponiendo que hicimos clic en el autor con el id 1). Como en la lista de autores se itera sobre el arreglo authors, podemos tener acceso al id de un autor con el atributo author.id
. De esta forma, el valor del atributo routerLink
quedaría así:
routerLink="/authors/{{ author.id }}"
El código del componente
BookDetailComponent
quedaría así:
<div class="container-fluid">
<p class="h3 p-3">{{ bookDetail.name }}</p>
<div class="row">
<div class="col-2">
<div class="thumb">
<img
class="img-fluid"
src="{{ bookDetail.image }}"
alt="{{ bookDetail.name }}"
/>
</div>
</div>
<div class="col">
<dl>
<dt>Authors</dt>
<dd class="caption" *ngFor="let author of bookDetail.authors">
<a routerLink="/authors/{{ author.id }}">{{ author.name }}</a>
</dd>
<dt>ISBN</dt>
<dd>{{ bookDetail.isbn }}</dd>
<hr />
<dt>Publishing Date</dt>
<dd>{{ bookDetail.publishingDate }}</dd>
<hr />
<dt class="bold">Editorial</dt>
<dd class="caption">{{ bookDetail.editorial.name }}</dd>
<hr />
<dt class="bold">Description</dt>
<dd>{{ bookDetail.description }}</dd>
</dl>
</div>
</div>
</div>
El componente AuthorDetailComponent
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 tomar el id del autor que llega por la ruta y enviar ese id a un método del servicio que retorne la información de ese autor, Para esto, debemos:
id
del autor lo traiga utilizando http.get
. Este método retorna un Observable
Observable
que retorna el servicio. Este es el código para el servicio AuthorService
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { AuthorDetail } from './author-detail';
import { environment } from './../../environments/environment.development';
@Injectable({
providedIn: 'root'
})
export class AuthorService {
private apiUrl: string = environment.baseUrl + 'authors';
constructor(private http: HttpClient) { }
getAuthors(): Observable<AuthorDetail[]> {
return this.http.get<AuthorDetail[]>(this.apiUrl);
}
getAuthor(id: string): Observable<AuthorDetail> {
return this.http.get<AuthorDetail>(this.apiUrl + "/" + id);
}
}
En el componente, además de la suscripción al Observable
, tenemos que identificar si el llamado fue 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
que ofrece servicios para obtener los valores de la ruta.
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { AuthorDetail } from '../author-detail';
import { AuthorService } from '../author.service';
@Component({
selector: 'app-author-detail',
templateUrl: './author-detail.component.html',
styleUrls: ['./author-detail.component.css']
})
export class AuthorDetailComponent implements OnInit {
authorId!: string;
@Input() authorDetail!: AuthorDetail;
constructor(
private route: ActivatedRoute,
private authorService: AuthorService
) {}
getAuthor(){
this.authorService.getAuthor(this.authorId).subscribe(author=>{
this.authorDetail = author;
})
}
ngOnInit() {
if(this.authorDetail === undefined){
this.authorId = this.route.snapshot.paramMap.get('id')!
if (this.authorId) {
this.getAuthor();
}
}
}
}
Con estos ajustes, cuando se hace clic en el nombre del autor la aplicación navega a la ruta /authors/id_autor
, el componente toma el id, se conecta al servicio y muestra el detalle de ese autor.
Esta navegación debe ocurrir cuando desde el detalle de un autor se hace click en el nombre de un libro.
routerLink
en el detalle del autor. La asociación de rutas a elementos como textos, imágenes, etc, sigue el mismo principio explicado antes: se incluye el elemento routerLink
con la ruta especificada. Para el caso de los libros esperamos una ruta de la forma /books/1
(suponiendo que hicimos clic en el book con el id 1). Como en la lista de libros se itera sobre el arreglo books, podemos tener acceso al id de un book con el atributo book.id
. De esta forma, el valor del atributo routerLink
quedaría así:
routerLink="/books/{{ book.id }}"
El código del componente AuthorDetailComponent
quedaría así:
<div class="panel-body">
<div class="border margin p-3">
<p class="h3 p-2 author-name">{{ authorDetail.name }}</p>
<p></p>
<div class="row">
<div class="col-md-3">
<div class="thumb">
<img
class="img-fluid img-thumbnail border border-danger"
src="{{ authorDetail.image }}"
alt="{{ authorDetail.name }}"
/>
</div>
</div>
<div class="col-md-7">
<dl>
<dt class="text-title">Bio</dt>
<dd class="text">{{ authorDetail.description }}</dd>
<dt class="text-title">BirthDay</dt>
<dd class="text">{{ authorDetail.birthDate }}</dd>
</dl>
<p class="h3">Books by this author:</p>
<ul>
<li
*ngFor="let book of authorDetail.books"
>
<a routerLink="/books/{{ book.id }}">{{ book.name }}</a>
</li>
</ul>
</div>
</div>
</div>
</div>
El componente BookDetailComponent
tiene un parámetro de entrada @Input()
que corresponde al libro que se va a desplegar. Sin embargo, en este llamado utilizando el routerLink
no enviamos el objeto BookDetail
sino que enviamos en la ruta el id
de dicho libro.
Entonces debemos tomar el id del libro que llega por la ruta y enviar ese id a un método del servicio que retorne la información de ese libro, Para esto, debemos:
id
del libro lo traiga utilizando http.get
. Este método retorna un Observable
Observable
que retorna el servicio. Este es el código para el servicio BookService
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from './../../environments/environment.development';
import { Observable } from 'rxjs';
import { BookDetail } from './bookDetail';
@Injectable({
providedIn: 'root'
})
export class BookService {
private apiUrl: string = environment.baseUrl + 'books';
constructor(private http: HttpClient) { }
getBooks(): Observable<BookDetail[]> {
return this.http.get<BookDetail[]>(this.apiUrl);
}
getBook(id: string): Observable<BookDetail> {
return this.http.get<BookDetail>(this.apiUrl + "/" + id);
}
}
En el componente, además de la suscripción al Observable
, tenemos que identificar si el llamado fue 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
que ofrece servicios para obtener los valores de la ruta.
Este es el contenido del archivo book-detail.component.ts
import { Component, Input, OnInit } from '@angular/core';
import { BookDetail } from '../bookDetail';
import { ActivatedRoute } from '@angular/router';
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 {
bookId!: string;
@Input() bookDetail!: BookDetail;
constructor(
private route: ActivatedRoute,
private bookService: BookService
) { }
getBook(){
this.bookService.getBook(this.bookId).subscribe(book=>{
this.bookDetail = book;
})
}
ngOnInit() {
if(this.bookDetail === undefined){
this.bookId = this.route.snapshot.paramMap.get('id')!
if (this.bookId) {
this.getBook();
}
}
}
}
Con estos ajustes, cuando se hace clic en el nombre del libro la aplicación navega a la ruta /books/bookId
, el componente toma el id, se conecta al servicio y muestra el detalle de ese libro.
Esta navegación debe ocurrir cuando desde la lista de editoriales se hace click en la carátula de un libro.
Este es el código del archivo que hay que crear en el módulo editorial con el nombre editorial-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { EditorialListComponent } from './editorial-list/editorial-list.component';
const routes: Routes = [
{
path: 'list',
component: EditorialListComponent
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class EditorialRoutingModule { }
Ahora, hay que actualizar el archivo de rutas de la aplicación app-routing.module.ts
como se ve a continuación:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { BookListComponent } from './book/book-list/book-list.component';
const routes: Routes = [
{ path: '', component: BookListComponent },
{ path: 'books', loadChildren: () => import('./book/book.module').then(m => m.BookModule) },
{ path: 'authors', loadChildren: () => import('./author/author.module').then(m => m.AuthorModule) },
{ path: 'editorials', loadChildren: () => import('./editorial/editorial.module').then(m => m.EditorialModule) }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}
Luego se debe importar el nuevo módulo de rutas de editorial en el módulo principal así:
...
imports: [
BrowserModule,
AppRoutingModule,
BookModule,
EditorialModule,
HttpClientModule,
AuthorModule,
BookRoutingModule,
AuthorRoutingModule,
EditorialRoutingModule
],
...
Por último importe RouterModule
y EditorialRoutingModule
al módulo de editorial:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { EditorialListComponent } from './editorial-list/editorial-list.component';
import { RouterModule } from '@angular/router';
import { EditorialRoutingModule } from './editorial-routing.module';
@NgModule({
imports: [
CommonModule,
RouterModule,
EditorialRoutingModule
],
declarations: [EditorialListComponent]
})
export class EditorialModule { }
routerLink
en la imagen del libro. Para el caso de los libros esperamos una ruta de la forma /books/1
(suponiendo que hicimos clic en el book con el id 1). Como en la lista de libros se itera sobre el arreglo books, podemos tener acceso al id de un book con el atributo book.id. De esta forma, el valor del atributo routerLink
quedaría así:
routerLink="/books/{{ book.id }}"
El código del componente EditorialListComponent
quedaría así:
<div class="container-fluid mt-2">
<h2 class="text-center">Editorials</h2>
<div class="row" *ngFor="let editorial of editorials">
<div class="col-2">
<p>{{ editorial.name }}</p>
</div>
<div class="col-10">
<div class="row">
<div
class="card p-2 mb-2"
style="width: 7rem; height: 10rem"
*ngFor="let book of editorial.books"
>
<img
class="card-img-top"
src="{{ book.image }}"
alt="{{ book.name }}"
routerLink="/books/{{ book.id }}"
/>
</div>
</div>
</div>
<hr />
</div>
</div>
El componente BookDetailComponent
ya está organizado para recibir el libro que debe mostrar, bien sea como parámetro del componente o como parámetro en de la ruta.
En este tutorial y comparado con el de maestro/detalle hemos agregado varios componentes adicionales:
AuthorListComponent
AuthorDetailComponent
EditorialListComponent
También hemos creado dos servicios adicionales:
AuthorService
EditorialService
Al ejecutar las pruebas por defecto aparecerán varios errores. Recuerde que para arreglar esos errores (siguiendo lo mencionado en los tutoriales de listar y detalle) se debe:
Importar el módulo HttpClientTestingModule
en la prueba de los servicios
Importar el módulo HttpClientModule
en la prueba de los componentes
Al agregar estos módulos aún seguirá apareciendo el siguiente error:
NullInjectorError: R3InjectorError(DynamicTestModule)[ActivatedRoute -> ActivatedRoute]:
NullInjectorError: No provider for ActivatedRoute!
Esto ocurre porque al utilizar routerLink
se agregó el módulo RouterModule
a cada módulo funcional; por tanto, se requiere importar en las pruebas de cada componente el módulo RouterTestingModule
.
Este es entonces el fragmento de código para la prueba del componente BookListComponent
. Ahí se observa en el arreglo imports la referencia a RouterTestingModule.
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule, RouterTestingModule],
declarations: [ BookListComponent ],
providers: [ BookService ]
})
.compileComponents();
}));
Incluya esa importación en todas las pruebas de los componentes de detalle y ejecute de nuevo las pruebas que deberán funcionar correctamente.