Al finalizar este tutorial el estudiante estará en capacidad de construir una aplicación Angular compuesta del módulo principal y de un módulo llamado CourseModule el cual declara un servicio y componente para desplegar una lista de cursos. Los pasos que siguen son:
El resultado final del tutorial es una aplicación que despliega la lista descrita en la Figura 1.
|
Figura 1. Apariencia de la aplicación desarrollada. |
Para realizar este taller Ud. debe tener claras los respuestas a las siguientes preguntas:
¿Qué es un componente en Angular?
¿Cuál es la relación entre aplicación, módulo y componente?
¿Qué es un decorador?
¿Cómo se define la relación entre módulos?
¿Cómo se define la relación entre módulos y componentes?
¿Qué es un selector en un componente?
¿Qué es una vista?
¿Qué es "invocar" un componente?
¿Cuáles son las etiquetas Angular para crear las vistas HTML?
Si no es así, por favor revise el material del curso antes de seguir. De esta forma podrá sacarle más provecho a este tutorial.
Cree una aplicación Angular que se llame, por ejemplo, tutorialcourses, siguiendo las instrucciones detalladas en el tutorial de Creación de Aplicación Angular.
Abra su aplicación en VSCode y:
src/index.html y modifique la etiqueta title por nombre del tutorial "Listar Cursos".app.html.Vamos a crear el módulo de course junto con el primer componente list-course que nos permitirá listar los cursos de esta aplicación básica. Para esto, utilizaremos la línea de comandos de angular (angular cli). Esto garantiza que se genere con la configuración tradicional y se integre correctamente al módulo principal (AppModule).
cd mynewapp
ng generate module course --type-separator=.
ng generate component course/course-list --type=component
Mediante angular-cli se generaron el nuevo módulo y su respectivo componente, esto creó una carpeta en src/app llamada course en donde se encuentra el archivo course.module.ts.
|
Figura 3. Contenido de la clase que representa el nuevo módulo |
En la Figura 3, en la línea 11 está la declaración de la clase CourseModule.
Lo que determina que esta clase es un módulo es el decorador @NgModule que empieza en la línea 5.
Note que dentro del decorador hay un objeto que tiene dos atributos:
imports cuyo valor es un arreglo de los nombres de los módulos que necesitadeclarations cuyo valor también es un arreglo de los nombres de los componentes que declara. Este se crea inicializador vacío pero al crear el componente CourseList este es agregado automáticamente por Angular CLI.En la línea 1 está el import de Angular para el decorador.
En la línea 2 está lo básico de Angular (CommonModule) que todos los módulos deben importar (importado dentro del decorador en la línea 7).
En la línea 6, dentro del decorador de módulo, está la declaración del componente que acabamos de crear dentro del módulo. En la línea 3 se importa el archivo del componente.
Ahora abra el archivo course-list.component.ts en la carpeta course-list creada en la carpeta del módulo para revisar el componente que se creó por defecto como parte del módulo (ver Figura 4).
|
Figura 4. Contenido de la clase que representa el nuevo componente. |
En la línea 9 está la declaración de la clase CourseListComponent.
Lo que determina que esta clase es un componente es el decorador @Component que empieza en la línea 3.
Dentro del decorador hay un objeto que tiene tres atributos:
selector cuyo valor es un string, que representa la etiqueta que se usará para "invocar" el componente directamente sobre alguna vista. standalone cuyo valor es false ya que indica que es una aplicación basada en módulos y no en componentes individuales. templateUrl cuyo valor es un string y representa el nombre del archivo que contiene la vista del componente.styleUrls cuyo valor es un arreglo de strings y contiene los nombres de archivos, si los hay, de estilos que se van a usar en este componente particular.En la línea 1 está el import de Angular para el decorador del componente (Component).
Para que la aplicación pueda utilizar el nuevo módulo, este se debe importar en el módulo principal AppModule, ubicado en el archivo app-module.ts (ver figura 5).
Para importar en el módulo principal el módulo CourseModule se debe realizar dos tareas:
import { CourseModule } from './course/course.module';
imports el nombre del módulo, es decir, de la clase. Vea la línea 15 de la figura 5.
|
Figura 5. Importación del nuevo módulo en el módulo principal. |
Siempre que incluimos un archivo debemos tener en cuenta la ruta donde se encuentra.
|
Figura 6. Estructura de archivos de la aplicación |
La Figura 7 sintetiza la relación entre los elementos hasta ahora creados.
|
Figura 7. Relación de los elementos creados en el proyecto |
Teniendo en cuenta lo que hemos realizado hasta el momento y el diagrama de clases anterior podemos repasar las respuestas a algunas de las preguntas:
¿Cuál es la relación entre aplicación, módulo y componente? | Una aplicación está compuesta de módulos. Por defecto siempre hay un módulo principal llamado Un módulo declara componentes. En el diagrama de clases:
|
¿Qué es un decorador? | Los decoradores son anotaciones sobre las clases o sobre otros elementos del lenguaje. La forma general es:
El decorador y la información en sus atributos determina estructura y comportamiento del elemento que está anotado. Hasta ahora en el ejemplo, hemos usado el decorado de clase para definirla como módulo o componente. En el diagrama de clases estamos representando estos decoradores, que son metadatos, como comentarios. |
¿Cómo se define la relación entre módulos? | La relación entre módulos, hasta ahora en el ejemplo, se define como Significa que el módulo que importa otro podrá "ver" los elementos que el módulo exporta. |
¿Cómo se define la relación entre módulos y componentes? | Un módulo declara componentes. También declara Los componentes pueden ser internos al módulo o el módulo los puede exportar para que otros módulos los utilicen. |
Antes de invocar el componente CourseListComponente, veamos cómo en el archivo index.html invoca el componente principal.
La Figura 8 presenta el contenido del index.html y el contenido del componente principal AppComponent.
Donde aparece el número 1 se está invocando el componente principal utilizando su selector.
|
Figura 8. Contenido de index.html y del componente principal. |
El selector está definido en la línea marcada con el número 2.
Lo que va a suceder en ejecución, es que, en el lugar donde se está utilizando el selector app-root, se va a reemplazar por el contenido del archivo (marcado con el número 3) app.html.
Siguiendo el mismo principio, lo que vamos a hacer es invocar el componente de los cursos dentro del HTML del componente principal.
CourseListComponent.app.html (ver Figura 9).
|
Figura 9. Inclusión del nuevo componente en el componente principal. |
Sigamos con las preguntas:
¿Qué es un selector en un componente? | Corresponde al nombre dado al atributo |
¿Qué es una vista? | La vista de un componente, es el código html que será usado cuando se invoque el componente. En este ejemplo, estamos utilizando el atributo |
¿Qué es "invocar" un componente? | Una de las formas de invocar un componente es utilizando su selector como etiqueta HTML en el lugar donde queremos que el componente se despliegue. |
Ejecutemos la aplicación utilizando un terminal de VSCode con el comando
ng serve
En la página debería desplegarse el encabezado Lista de cursos del estudiante, pero no aparece nada.
Al abrir la consola de VSCode veremos el siguiente error (ver Figura 10):
|
Figura 10. Error al ejecutar la aplicación |
¿Por qué dice que app-course-list no es un elemento conocido? Veamos el diagrama de clases descrito en la Figura 11.
|
Figura 11. Diagrama de clase. |
Para que el componente CourseListComponent que declara el módulo CourseModule, sea visible desde la vista del componente AppComponent, CourseModule debe exportarlo.
Por tanto, debemos incluir exports sobre la relación entre CouseModule y CourseListComponent (ver Figura 12).
|
Figura 12. Inclusioń de la relación exports entre CourseModule y CourseComponent |
A nivel de código necesitamos agregar en el decorador del módulo el atributo exports con la referencia al componente.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CourseListComponent } from './course-list/course-list.component';
@NgModule({
declarations: [CourseListComponent],
imports: [CommonModule],
exports: [CourseListComponent],
})
export class CourseModule {}
Volvemos a ejecutar la aplicación y debemos obtener en en navegador la salida descrita en la Figura 13.
|
Figura 13. Vista de la aplicación en el navegador |
Ahora la pregunta es de dónde aparece el texto course works !.
Abramos el archivo de la vista del componente CourseListComponent. Es decir el archivo course-list.html y aquí tenemos el mensaje que se desplegó (ver Figura 14). En los próximos pasos vamos a cambiar este mensaje por el despliegue de la lista de cursos.
|
Figura 14. Contenido del nuevo componente. |
En Angular, los componentes siguen una arquitectura Model, View, Controller (MVC). El modelo corresponde a la información que se va a desplegar al usuario o a recibir del usuario a través de la vista del componente. La vista es el HTML que se despliega en la aplicación y el controlador es la clase del componente que se ocupa de proveer la información del modelo, de atender las acciones del usuario (por ejemplo cuando hace clic en algún botón, y debe mantener la vista actualizada (esto lo hace automáticamente Angular cuando se define el modelo).
Veamos estos conceptos en el ejemplo. El modelo es la información de la lista de cursos. El controlador debe tener una forma de obtener esa info, la vista despliega la lista de cursos. En ese ejemplo, como no hay interacción con el usuario, el controlador no es responsable de nada más.
En los siguientes pasos explicamos cómo construir estos elementos.
Primero vamos a crear una clase Course que contenga la información de un curso. Un curso tiene un nombre, el nombre del profesor y el número de créditos.
Para crear la clase, desde la línea de comando ejecutamos:
ng generate class course/course --skip-tests true
Como resultado del comando, podemos ver que se creó el archivo course.ts dentro de la carpeta course.
La clase tendrá los atributos y el constructor.
export class Course {
name: string;
professor: string;
credits: number;
public constructor(name: string, professor: string, credits: number) {
this.name = name;
this.professor = professor;
this.credits = credits;
}
}
Ahora que tenemos la clase que representa los cursos, podemos declarar, dentro de la clase del componente, un arreglo para los cursos:
courses: Array<Course> = [];
Nos aparece que Course no está definido, entonces debemos importarlo:
import { Component } from '@angular/core';
import { Course } from '../course';
@Component({
selector: 'app-course-list',
standalone: false,
templateUrl: './course-list.component.html',
styleUrl: './course-list.component.css',
})
export class CourseListComponent {
courses: Array<Course> = [];
}
Hasta acá el arreglo está undefined. Para resolver esto, tenemos que inicializar el arreglo.
En este paso, vamos primero a darle un valor utilizando unos datos construidos directamente en el código.
Vamos a declarar un arreglo que contiene los cursos. Esto lo hacemos en un archivo (New File desde VSCode) que se encuentre en la carpeta course y que se llame dataCourses.ts.
En este archivo declaramos e inicializamos el arreglo con algunos datos de los cursos. Como estamos creando objetos de la clase Course, debemos importar esta clase (primera línea del archivo).
import { Course } from "./course";
export const dataCourses = [
new Course("Ingeniería de software", "Martin Fowler", 4),
new Course("Fútbol 1", "Freddy Rincón", 2),
new Course("Algoritmos", "Dennis Ritchie", 2),
new Course("Estructuras de datos", "Yesid Donoso", 2),
new Course("Fútbol 2", "James Rodríguez", 6),
];
En el componente course-list vamos a agregar el método que consulta los datos creados en el archivo dataCourses.ts y los asignaremos a la variable courses.
Primero agregaremos al componente course-list.component.ts las siguientes líneas de código.
export class CourseListComponent implements OnInit {
courses: Array<Course> = [];
constructor() {}
ngOnInit() {}
}
Actualice la importación de la línea 1 con la interface OnInit como se muestra a continuación
import { Component, OnInit } from '@angular/core';
Al añadir estas líneas de código, el componente implementa la interface OnInit. Esta interface define la función ngOnInit() que se llamará cada vez que se cree el componente.
Una vez implementada la interface OnInit en el componente course-list añadiremos la función que actualice los datos de la variable courses. Modifique courses-list.component.ts añadiendo la siguiente función y modificando la función ngOnInit()como se muestra a continuación.
getCoursesList(): Array<Course> {
return dataCourses;
}
dataCourses es la variable que definimos en el archivo dataCourses.ts entonces, tenemos que importarlo.
En la función que se va a llamar cuando se cree el componente, es decir, función ngOnInit() actualizamos la variable del componente que contiene la información.
Esta variable es la que va a acceder la vista del componente como veremos en el siguiente paso.
import { Component, OnInit } from '@angular/core';
import { Course } from '../course';
import { dataCourses } from '../dataCourses';
@Component({
selector: 'app-course-list',
standalone: false,
templateUrl: './course-list.component.html',
styleUrl: './course-list.component.css',
})
export class CourseListComponent implements OnInit {
courses: Array<Course> = [];
constructor() {}
getCoursesList(): Array<Course> {
return dataCourses;
}
ngOnInit() {
this.courses = this.getCoursesList();
}
}
Como ya hemos explicado, la vista es el HTML asociado con el componente. El objetivo es desplegar la lista de cursos en una tabla.
El siguiente código muestra una tabla y su encabezado. Note que el archivo contiene sólo la parte de HTML que se va a desplegar. No es un archivo completo HTML dado que NO tiene las etiquetas head y tampoco body.
<div class="container-fluid">
<div class="col-6">
<table class="table">
<thead>
<tr>
<th class="h4">Course's Name</th>
<th class="h4">Professor's Name</th>
<th class="h4">Credits</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
El contenido dinámico de la tabla lo vamos a crear al interior de las etiquetas:
<tbody>
</tbody>
Angular ofrece una serie de extensiones a HTML, representadas en nuevas etiquetas y nuevos atributos para facilitar la construcción del contenido dinámico de la página.
En este ejemplo vamos a ver dos de estas extensiones:
La instrucción es un atributo que se define sobre la etiqueta donde queremos que empiece el ciclo. El ciclo termina donde esa misma etiqueta se cierra.
La forma general es:
@for (iter of coleccion; track iter){ ... }
Donde:
iter es una variable local al ciclocoleccion es un atributo que DEBE estar definido en el componente (es parte del modelo)Así, en nuestro código, queremos construir una fila de la tabla, etiqueta La variable c es el iterador del ciclo que va tomando el valor de cada elemento desde principio a fin. En el cuerpo del ciclo queremos desplegar los datos de cada elemento. Para desplegar un valor de un objeto, utilizamos la expresión Veamos el ejemplo: Así, el resultado de la aplicación hasta ahora es el descrito en la Figura 15. Figura 15. Vista de la aplicación desarrollada. En este paso vamos a obtener un conjunto de datos mediante el llamado a un servicio Para esto debemos: Los servicios son clases que ofrecen funciones que serán utilizadas por componentes u otros elementos Angular. ¿Cómo organizar las clases de servicios? Esta es una decisión de diseño. Las clases de servicios deben ser cohesivas, es decir, ocuparse de un solo tipo de responsabilidad. En este ejemplo, vamos a crear un servicio que se ocupa de obtener el modelo para el componente de los cursos. Dado que un servicio es una clase, si otra clase tiene acceso al servicio, lo podría crear. Sin embargo, la creación de los servicios es responsabilidad de Angular y funciona de la siguiente manera: Vamos a crear el servicio desde la carpeta línea de comandos. De nuevo usamos Figura 16. Uso de Angular-cli para crear un servicio. El archivo Note la anotación Nuestro servicio va a definir una función La inyección del servicio se hace declarando el atributo en el constructor e importando el archivo de la clase correspondiente: La función Vamos a suponer que el valor de esa URL está almacenado en el atributo El valor de la URL de base donde se encuentra el back-end lo vamos a declarar dentro de un archivo llamado Si aún no tiene la carpeta de environments puede generarla con el siguiente comando: Figura 17. Carpeta environments. En El contenido del archivo En este ejemplo, baseUrl lleva a un json que está guardado en un repositorio de github. Incluya la url de este archivo en baseUrl: Todas las funciones HTTP son asíncronas, es decir se hace el llamado, pero la aplicación que llamó, en este caso el front, sigue su curso. Por esta razón, estas funciones devuelven el resultado en un objeto El código completo del servicio es el siguiente: Nuestro componente debe ahora llamar la función creada en el servicio. Para esto tenemos que declarar el servicio para que Angular lo inyecte y podamos usar la función Para poder usar el servicio en el componente necesitamos declararlo en el constructor e importar el archivo. Un Cuando el método asíncrono termina, se va a ejecutar la suscripción. La suscripción recibe una función y esa función tiene como parámetro el resultado del método asíncrono y en el cuerpo de la función lo que queremos hacer con ese resultado. Si el resultado está en la variable courses (los cursos que devolvió el servicio), lo que queremos hacer es actualizar el atributo de la clase. Entonces, tenemos que cambiar el método del componente El código completo del componente se puede ver aquí: Al ejecutar la aplicación nos aparece el siguiente error en la consola del navegador (ver Figura 20). Figura 20. Error en la consola del navegador. Esto se debe a que en el caso del servicio De nuevo podemos ejecutar la aplicación y obtener el resultado esperado. Note que hay muchos más datos en este resultado. Los interceptors son usados para interceptar y modificar peticiones y respuesta HTTP antes que lleguen a su destino final. Esto nos permite ejecutar pruebas como añadir headers a las peticiones, manejo de errores o llevar una traza de los errores. Primero, instalaremos la librería Toast en el proyecto. Desde la línea de comandos, instale la nueva librería Esta librería requiere de la librería Los interceptos nos ayudarán a notificar de manera gráfica a los usuarios si alguna petición falló. Adicionalmente nos mostrarán el error enviado desde el back cuando alguna petición está fallando. Para crear nuestra carpeta de interceptos debemos ejecutar la siguiente línea de comandos: Esto creará una carpeta llamada Figura 21. Carpeta interceptos. Copie el siguiente código en el archivo Y copie el siguiente código en el archivo Para agregar los interceptors en todas las peticiones hechas desde la aplicación, debemos importarlos en el app. Para esto agregaremos en los imports del Esto nos permitirá hacer la respectiva importación de la librería gráfica de los interceptors en nuestra aplicación. Pero debemos agregar en la sección de los providers de Por último, debemos actualizar los estilos de la aplicación en el archivo angular.ts y añadir la siguiente línea de código Para probar los interceptors, modificaremos momentáneamente la url del archivo environment para intentar hacer una petición a un servicio que no existe. Modifique el archivo environment.development.ts para que quede de la siguiente manera: Corra el servidor (ng serve) y al ejecutarlo deberá aparecer un mensaje de error de conección en la esquina inferior derecha de su aplicación como se muestra en la figura 22 a continuación Figura 22. Error de conección al api Para agregar bootstrap en nuestro proyecto debemos ejecutar el comando Para agregar los estilos Bootstrap al proyecto debemos ir al archivo Figura 23. Inclusión de los estilos de Bootstrap en el archivo angular.json. Ahora podemos ejecutar de nuevo Figura 24. Aplicación final. El siguiente paso es probar que el componente está renderizando los datos correctamente. Primero debemos ejecutar las pruebas por defecto con el comando Esto nos indica que el servicio Para esto en el archivo course.service.spec.ts debe agregar la referencia Al ejecutar de nuevo la prueba tendremos este otro error que indica que en la prueba del componente principal no hay una referencia al componente Para esto en el atributo declarations de la prueba agregue la referencia al componente así: Luego aparecerá otro error de este tipo: Dado que el componente Finalmente aparecerá un error en un spec. Esto ocurre porque en la aplicación por defecto se espera que se muestre en la página el mensaje "'Hello, tutorialcourses"; no obstante el contenido del componente principal fue reemplazado y este mensaje no aparece. Por tanto podemos eliminar ese spec para que la prueba se ejecute correctamente. Al final este será el contenido del archivo Ahora luego de solucionar todos los errores previos es el momento de crear la prueba para el componente Para esto instale la librería Faker con el comando En esta prueba se verifica que se haya renderizado una tabla con al menos un nodo como hijo, en este caso, una fila. También se verifica que exista un elemento dd con el valor correspondiente al nombre del curso. Como conclusión del tutorial tenemos el diagrama completo de clases del ejemplo, que se presenta en la Figura 25. Figura 25. Diagrama de clases completo. , por cada elemento en el arreglo courses que definimos en el componente.
@for (c of courses; track c) {
// Cuerpo del ciclo
}{{ objeto.atributo }}. @for (c of courses; track c) {
<tr>
<td>
<dd>{{ c.name }}</dd>
</td>
<td>
<dd>{{ c.professor }}</dd>
</td>
<td>
<dd>{{ c.credits }}</dd>
</td>
</tr>
}

HTTP GET.
HttpClientModule de angular y su servicio HttpClient. Esto nos permite de una manera sencilla hacer llamados HTTP (get, post, put, delete).Observable (asíncrono)Crear un servicio
@Injectable(...))Angular cli y el resultado deben ser dos archivos como se ve en la Figura 16.ng generate service course/course --type=service

course.service.ts resultante debe incluir:import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class CourseService {
}@Injectable. El atributo providedIn: ‘root' significa que el servicio podrá ser utilizado en cualquier parte de la aplicación. Utilizar HttpClient
getCourses() que obtiene los cursos desde un sitio (que usualmente es un endpoint de un back-end, como los creados por su equipo en los controllers del back) utilizando HTTP GET. Para esto, vamos a inyectar un servicio definido por angular que se llama HttpClient. import { HttpClient } from '@angular/common/http';
...
export class CourseService {
constructor(private http: HttpClient) { }
...
}getCourses() que vamos a incluir en el servicio va a utilizar HTTP para invocar el GET. Para esto se necesita conocer la URL donde está el servidor que provee los cursos (es decir, el back-end). apiUrl. Entonces, la función se debe llamar así:this.http.get(this.apiUrl)Crear la variable de ambiente URL
environment.development.ts dentro de una carpeta environments en src (ver Figura 17).ng generate environments

environments podemos ver otro archivo, el de producción cuyo nombre es environment.ts. Este tendrá los valores de las url de producción y en el otro archivo (environment.development.ts) estarán las urls de desarrollo.environment.development.ts es un objeto con atributos de configuración. En particular está el atributo baseUrl (ver Figura 18) que debemos definir para componer la url final. export const environment = {
production: false,
baseUrl: ''
}
export const environment = {
production: false,
baseUrl: 'https://gist.githubusercontent.com/josejbocanegra/9bc286433e85ad2fdd3b4d3b2a1998f8/raw/ab432ff4f10f767a8c997a8e15012cd7d908dd62/'
}
Observables y asincronía
Observable. /src/app/course/course.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Course } from './course';
import { environment } from '../../environments/environment.development';
@Injectable({
providedIn: 'root'
})
export class CourseService {
private apiUrl = environment.baseUrl + 'courses.json';
constructor(private http: HttpClient) { }
getCourses(): Observable<Course[]> {
return this.http.get<Course[]>(this.apiUrl);
}
}Suscribirse al observable del servicio
...
import { CourseService } from '../course.service';
@Component({
selector: 'app-course-list',
standalone: false,
templateUrl: './course-list.html',
styleUrl: './course-list.css',
})
export class CourseListComponent implements OnInit {
constructor(private courseService: CourseService) {}
...
}Observable tiene una función de suscripción, esto significa que quien llama al observable, se suscribe a él. courses => {this.courses = courses; }getCourses() así: getCoursesList() {
this.courseService.getCourses().subscribe((courses) => {
this.courses = courses;
});
}/src/app/course/course-list.ts
import { Component, OnInit } from '@angular/core';
import { Course } from '../course';
import { dataCourses } from '../dataCourses';
import { CourseService } from '../course.service';
@Component({
selector: 'app-course-list',
standalone: false,
templateUrl: './course-list.component.html',
styleUrl: './course-list.component.css',
})
export class CourseListComponent implements OnInit {
courses: Array<Course> = [];
constructor(private courseService: CourseService) {}
getCoursesList(): Array<Course> {
this.courseService.getCourses().subscribe((data) => {
this.courses = data;
});
return this.courses;
}
ngOnInit() {
this.getCoursesList();
}
}Ejecutar la aplicación y resolver el error

HttpClient que estamos utilizando en el servicio CourseService, no es suficiente con inyectar e importar la clase. Debemos también hacer una importación del módulo en el módulo principal de la aplicación. Así: src/app/app-module.ts
import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing-module';
import { App } from './app';
import { CourseModule } from './course/course-module';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [App],
imports: [BrowserModule, AppRoutingModule, CourseModule, HttpClientModule],
providers: [provideBrowserGlobalErrorListeners()],
bootstrap: [App],
})
export class AppModule {}Preparación de librerías
npm i ngx-toastr@angular/animations por lo que también instalaremos esta librería. Instale la librería de animaciones ejecutando el siguiente comando en la línea de comandos.npm i @angular/animationsAgregar interceptors al proyecto
ng generate service interceptors/http-error-interceptor.serviceinterceptors dentro de la carpeta src/app/ (ver figura 21)

http-error-interceptor.service.tsimport { HttpEvent, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { ToastrService } from 'ngx-toastr';
import { Injectable } from '@angular/core';
@Injectable()
export class HttpErrorInterceptorService extends HttpErrorResponse {
constructor(private toastrService: ToastrService) {
super(toastrService);
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError((httpErrorResponse: HttpErrorResponse) => {
let errorMesagge = '';
if (httpErrorResponse.error instanceof ErrorEvent) {
errorMesagge = httpErrorResponse.error.error;
} else {
let errorType = 'Server side error';
if (httpErrorResponse.status === 0) {
errorMesagge = 'No hay conexión con el servidor';
} else {
errorMesagge = `${httpErrorResponse.status}: ${httpErrorResponse.error.error}`;
}
this.toastrService.error(errorMesagge, errorType, { closeButton: true });
}
return throwError(() => new Error(errorMesagge));
})
);
}
}http-error-interceptor.service.spec.ts.import { TestBed, inject } from '@angular/core/testing';
import { ToastrModule } from 'ngx-toastr';
import { HttpErrorInterceptorService } from './http-error-interceptor.service';
describe('Service: HttpErrorInterceptor', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ToastrModule.forRoot()],
providers: [HttpErrorInterceptorService],
});
});
it('should ...', inject([HttpErrorInterceptorService], (service: HttpErrorInterceptorService) => {
expect(service).toBeTruthy();
}));
});Utilizar Interceptors en la aplicación
app-module.ts las siguientes líneas de código:@NgModule({
declarations: [App],
imports: [
BrowserModule,
AppRoutingModule,
CourseModule,
HttpClientModule,
ToastrModule.forRoot({
timeOut: 10000,
positionClass: 'toast-bottom-right',
preventDuplicates: true,
}),
],app-module.ts el llamado a cada una de las peticiones. Para esto agregue las siguientes líneas de código:providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: HttpErrorInterceptorService,
multi: true,
},
],"styles": [
"node_modules/ngx-toastr/toastr.css",
"src/styles.css"
]Probar los Interceptors
export const environment = {
production: false,
baseUrl: 'http://localhost:8080/api',
};

npm i bootstrapAgregar los estilos bootstrap al proyecto
angular.json y en el atributo styles agregar la referencia a los estilos de Bootstrap locales que acabamos de instalar (ver Figura 23).node_modules/bootstrap/dist/css/bootstrap.min.css

Ejecutar de nuevo
ng serve y obtendremos la lista descrita en la Figura 24.

ng test. No obstante, aparecerán varios errores, como el siguiente:
CourseService se debe incluir la una referencia a Http. HttpClientTestingModule así:import { TestBed, inject } from '@angular/core/testing';
import { CourseService } from './course.service';
import { HttpClientTestingModule } from "@angular/common/http/testing";
describe('Service: Course', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [CourseService]
});
});
it('should ...', inject([CourseService], (service: CourseService) => {
expect(service).toBeTruthy();
}));
});CourseList. 
declarations: [
AppComponent, CourseComponent
],
CourseComponent tiene una referencia a un servicio que usa http se debe incluir en la prueba del componente principal una referencia al módulo HttpClientModule. imports: [
RouterTestingModule, HttpClientModule
],app.component.spec.tsimport { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
import { CourseComponent } from './course/course.component';
import { HttpClientModule } from '@angular/common/http';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
RouterTestingModule, HttpClientModule
],
declarations: [
AppComponent, CourseComponent
],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'tutorialcourses'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('tutorialcourses');
});
});CourseComponent. Lo que se busca en la prueba es generar un nuevo arreglo de cursos con valores ficticios (proporcionados por la librería Faker) y comprobar que estos valores se renderizan en la vista del componente.npm install @faker-js/faker --save-dev. Luego cree un nuevo archivo llamado course.component.spec.ts dentro del módulo course con el siguiente contenido:import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { faker } from '@faker-js/faker';
import { HttpClientModule } from '@angular/common/http';
import { CourseComponent } from './course.component';
import { CourseService } from './course.service';
import { Course } from './course';
describe('BookListComponent', () => {
let component: CourseComponent;
let fixture: ComponentFixture<CourseComponent>;
let debug: DebugElement;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule],
declarations: [ CourseComponent ],
providers: [ CourseService ]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CourseComponent);
component = fixture.componentInstance;
component.courses = [
new Course(faker.lorem.sentence(), faker.person.fullName(), faker.number.int())
]
fixture.detectChanges();
debug = fixture.debugElement;
});
it('should create', () => {
expect(component).toBeTruthy();
});
it("Component has a table", () => {
expect(debug.query(By.css("tbody")).childNodes.length).toBeGreaterThan(0);
});
it('should have an dd element ', () => {
const dd = debug.query(By.css('dd'));
const content: HTMLElement = dd.nativeElement;
expect(content.textContent).toEqual(component.courses[0].name)
});
});
