¿Qué aprenderá?

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:

  1. Desplegar la lista de cursos (sin Bootstrap) a partir de un arreglo inicializado directamente con la información dentro de la aplicación.
  2. Cambiar la forma como se obtiene la información de los cursos. Los cursos se obtienen con un servicio que se conecta vía HTTP GET a un sitio que los provee, este servicio retorna una colección de objetos json.
  3. Probar el método del servicio
  4. Agregar Bootstrap para mejorar la apariencia visual de la aplicación
  5. Probar el componente

¿Qué construirá?

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.

¿Qué necesita?

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:

  1. Cambie el título de la página: Vaya al archivo src/index.html y modifique la etiqueta title por nombre del tutorial "Listar Cursos".
  2. Borre el contenido del archivo app.component.html.

Para crear el nuevo módulo utilizamos la extensión Angular Files que está integrada dentro de VSCode.

Para esto, vaya a la carpeta app, clic derecho, Generate Module. El nombre del nuevo módulo es course (ver Figura 2).

Figura 2. Creación de un nuevo módulo.

Mediante angular-cli se generó la clase del nuevo módulo que se encuentra dentro del 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:

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 9, dentro del decorador de módulo, está la declaración del componente que se crea por defecto al crear un módulo. En la línea 3 se importa el archivo del componente.

Ahora abra el archivo course.component.ts 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 8 está la declaración de la clase CourseComponent.

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:

  1. selector cuyo valor es un string, que representa la etiqueta que se usará para "invocar" el componente directamente sobre alguna vista.
  2. templateUrl cuyo valor es un string y representa el nombre del archivo que contiene la vista del componente.
  3. 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).

También importa el código de la interface OnInit . El componente implementa la interface OnInit. Esta interface define la función ngOnInit() que se llamará cada vez que se cree el componente.

En la línea 12 podemos ver que, por defecto, la función está vacía.

En la línea 10 está el constructor del componente que también está vacío.

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';

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 AppModule.

Un módulo declara componentes.

En el diagrama de clases:

AppModule declara el componente AppComponent.

CourseModule declara el componente CourseComponent.

¿Qué es un decorador?

Los decoradores son anotaciones sobre las clases o sobre otros elementos del lenguaje.

La forma general es:

NombreDecorador(objeto con atributos y valores)

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 imports.

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 directivas y pipes pero esos elementos no están en este ejemplo.

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 CourseComponent, 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.component.html.

Siguiendo el mismo principio, lo que vamos a hacer es invocar el componente de los cursos dentro del HTML del componente principal.

  1. Buscamos el nombre del selector de CourseComponent.
  2. Lo utilizamos en app.component.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 selector, en el decorador Component, de la clase del componente.

¿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 templateUrl en el decorador Component, de la clase del componente. El valor del atributo es el nombre del archivo que contiene el código HTML de la vista del componente.

¿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 no es un elemento conocido? Veamos el diagrama de clases descrito en la Figura 11.

Figura 11. Diagrama de clase.

Para que el componente CourseComponent 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 CourseComponent (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 { CourseComponent } from './course.component';

@NgModule({
  imports: [
    CommonModule
  ],
  declarations: [CourseComponent],
  exports: [CourseComponent]
})
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 CourseComponent. Es decir el archivo course.component.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 hay que ir a la carpeta course y desde allí clic derecho luego seleccionar Generate Class y darle como nombre course

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;
 }
}

Asociar el modelo con el componente

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, OnInit } from '@angular/core';
import { Course } from './course';

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

 courses: Array<Course> = [];
 constructor() { }

 ngOnInit() {
 }

}

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.

Crear los datos para el modelo

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 controlador creamos un método público que retornará la lista de cursos.

  courses: Array<Course>;
  getCourseList(): 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 ngInit() 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',
  templateUrl: './course.component.html',
  styleUrls: ['./course.component.css']
})
export class CourseComponent implements OnInit {
  courses: Array<Course> = [];
  constructor() { }
  getCourseList(): Array<Course> {
    return dataCourses;
  }
  ngOnInit() {
    this.courses = this.getCourseList();
  }
}

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 encabezado

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:

  1. Cómo escribir un ciclo dentro de HTML
  2. Cómo acceder a un valor de un objeto para que quede desplegado en el HTML

Un ciclo dentro de HTML

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:

*ngFor="let iter of coleccion"

Donde:

Así, en nuestro código, queremos construir una fila de la tabla, etiqueta tr, por cada elemento en el arreglo courses que definimos en el componente.

La variable c es el iterador del ciclo que va tomando el valor de cada elemento desde principio a fin.

 
<tr *ngFor="let c of courses">
    // Cuerpo del ciclo
 </tr>

En el cuerpo del ciclo queremos desplegar los datos de cada elemento. Para desplegar un valor de un objeto, utilizamos la expresión {{ objeto.atributo }}.

Veamos el ejemplo:

     <tr *ngFor="let c of courses">
          <td>
            <dd>{{c.name}}</dd>
          </td>
          <td>
            <dd>{{c.professor}}</dd>
          </td>
          <td>
            <dd>{{c.credits}}</dd>
          </td>
      </tr>

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 HTTP GET.

Para esto debemos:

  1. Crear un servicio y entender la inyección de dependencias en Angular
  2. Utilizar el módulo HttpClientModule de angular y su servicio HttpClient. Esto nos permite de una manera sencilla hacer llamados HTTP (get, post, put, delete).
  3. Crear un método en el servicio que retorna un objeto Observable (asíncrono)
  4. Crear una variable de ambiente
  5. Probar el servicio
  6. Invocar con una suscripción el método del servicio desde el componente para que obtenga los datos (en este caso, cursos).

Crear un servicio

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 course. De nuevo usamos Angular Files (ver Figura 16).

Figura 16. Uso de Angular-cli para crear un servicio.

El archivo resultante es:

import { Injectable } from '@angular/core';
@Injectable({
  providedIn: 'root'
})
export class CourseService {
constructor() { }
}

Note la anotación @Injectable. El atributo providedIn: ‘root' significa que el servicio podrá ser utilizado en cualquier parte de la aplicación.

Utilizar HttpClient

Nuestro servicio va a definir una función getCourses() que obtiene los cursos desde un sitio (que usualmente es un endpoint de un back-end) utilizando HTTP GET. Para esto, vamos a inyectar un servicio definido por angular que se llama HttpClient.

La inyección del servicio se hace declarando el atributo en el constructor e importando el archivo de la clase correspondiente:

import { HttpClient } from '@angular/common/http';
...
export class CourseService {
    constructor(private http: HttpClient) { }
...
}

La función 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).

Vamos a suponer que el valor de esa URL está almacenado en el atributo apiUrl. Entonces, la función se debe llamar así:

this.http.get(this.apiUrl)

Observables y asincronía

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 Observable.

El código completo del servicio es el siguiente:

/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);
  }
}

Crear la variable de ambiente URL

El valor de la URL de base donde se encuentra el back-end lo vamos a declarar dentro de un archivo llamado environment.development.ts dentro de una carpeta environments en src (ver Figura 17).

Si aún no tiene la carpeta de environments puede generarla con el siguiente comando:

ng generate environments

Figura 17. Carpeta environments.

En 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.

El contenido del archivo 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: ''
}

En este ejemplo, baseUrl lleva a un json que está guardado en un repositorio de github. Incluya la url de este archivo en baseUrl:

https://gist.githubusercontent.com/josejbocanegra/9bc286433e85ad2fdd3b4d3b2a1998f8/raw/ab432ff4f10f767a8c997a8e15012cd7d908dd62/

Suscribirse al observable del servicio

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.

...
import { CourseService } from './course.service';

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

  constructor(private courseService: CourseService) { }
...
}

Un Observable tiene una función de suscripción, esto significa que quien llama al observable, se suscribe a él.

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.

courses => {this.courses = courses; }

Entonces, tenemos que cambiar el método del componente getCourses() así:

getCourses() {
    this.courseService.getCourses().subscribe(courses => {
      this.courses = courses;
    });
}

El código completo del componente se puede ver aquí:

/src/app/course/course.component.ts

import { Component, OnInit } from '@angular/core';
import { Course } from './course';
import { dataCourses } from './dataCourses';
import { CourseService } from './course.service';

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

  constructor(private courseService: CourseService) { }
  courses: Array<Course>;

  getCourses() {
    this.courseService.getCourses().subscribe(courses => {
      this.courses = courses;
    });
  }
  ngOnInit() {
    this.getCourses();
  }
}

Ejecutar la aplicación y resolver el error

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 HttpClient que estamos utilizando en el servicio CourseService, no es suficiente con inyectarlo 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 { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';
import { CourseModule } from './course/course.module';


@NgModule({
   declarations: [
      AppComponent
   ],
   imports: [
      BrowserModule,
      CourseModule,
      HttpClientModule
   ],
   providers: [],
   Bootstrap: [
      AppComponent
   ]
})
export class AppModule { }

De nuevo podemos ejecutar la aplicación y obtener el resultado esperado. Note que hay muchos más datos en este resultado.

Para agregar bootstrap en nuestro proyecto debemos ejecutar el comando npm install bootstrap

Agregar los estilos bootstrap al proyecto

Para agregar los estilos Bootstrap al proyecto debemos ir al archivo 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

Figura 23. Inclusión de los estilos de Bootstrap en el archivo angular.json.

Ejecutar de nuevo

Ahora podemos ejecutar de nuevo ng serve y obtendremos la lista descrita en la Figura 24.

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 ng test. No obstante, aparecerán varios errores, como el siguiente:

Esto nos indica que el servicio CourseService se debe incluir la una referencia a Http.

Para esto en el archivo course.service.spec.ts debe agregar la referencia HttpClientTestingModule así:

/* tslint:disable:no-unused-variable */

import { TestBed, async, 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();
 }));
});

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 CourseComponent.

Para esto en el atributo declarations de la prueba agregue la referencia al componente así:

declarations: [
       AppComponent, CourseComponent
     ],

Luego aparecerá otro error de este tipo:

Dado que el componente 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
     ],

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 app.component.spec.ts

import { 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');
  });

});

Ahora luego de solucionar todos los errores previos es el momento de crear la prueba para el componente 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.

Para esto instale la librería Faker con el comando npm install @faker-js/faker --save-dev. Luego cree el archivo course.component.spec.ts dentro del módulo course con el siguiente contenido:

/* tslint:disable:no-unused-variable */
import { async, 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(async(() => {
   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)
 });


});

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.