¿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 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 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 aplicación angular-cli 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, a partir de dónde lo vamos a incluir.

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 y el decorador de 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 se 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.

Figura 10. Error en la consola del navegador.

¿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 al ún botón, y de 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, 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:

private 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 {

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

Hasta acá el arreglo está undefined. Para resolver esto, tenemos que inicializar el arreglo.

En este paso, vamos primero a darle 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),
];

El controlador, que es la clase del componente, debe ocuparse de tener acceso al modelo.

Para esto definimos un atributo privado courses y un método público que lo retorna.

private 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 {

  constructor() { }
  courses: Array<Course>;
  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 }}.

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 llamos 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-cli (ver Figura 16).

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

El archivo generado resultado 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';

@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 del archivo environment.ts que está en la carpeta environments (ver Figura 17).

Figura 17. Carpeta environments.

Vemos que allí hay dos archivos. El de producción que tendrá los valores de las url en producción y el de desarrollo que es el que estamos actualizando.

El contenido de ese archivo es un objeto con atributos de configuración. En particular está el atributo baseUrl (ver Figura 18) que hemos definido para componer la url final. En este ejemplo, esta url lleva a un json que está guardado en un repo de github.

Figura 18. Contenido del archivo environments.

Desde nuestro servicio incluimos este archivo y componemos la url final utilizando el siguiente valor:

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

Probar el servicio

Tomando como referencia el tutorial sobre pruebas unitarias en Angular, se crea la prueba para el servicio.

En este código muestra la prueba desarrollada. En resumen, estamos verificando que el método getCourses del servicio efectivamente retorne un arreglo de cursos con el formato esperado.

import { TestBed, getTestBed } from "@angular/core/testing";

import {
 HttpTestingController,
 HttpClientTestingModule,
} from "@angular/common/http/testing";

import faker from "faker";

import { CourseService } from "./course.service";
import { Course } from "./course";
import { environment } from "../../environments/environment";

describe("CourseService", () => {
 let injector: TestBed;
 let service: CourseService;
 let httpMock: HttpTestingController;
 let apiUrl = environment.baseUrl + "courses.json";

 beforeEach(() => {
   TestBed.configureTestingModule({
     imports: [HttpClientTestingModule],
     providers: [CourseService],
   });

   injector = getTestBed();
   service = injector.get(CourseService);
   httpMock = injector.get(HttpTestingController);
 });

 afterEach(() => {
   httpMock.verify();
 });

 it("getCourses() should return 10 records", () => {
   let mockPosts: Course[] = [];

   for (let i = 0; i < 10; i++) {
     let course = new Course(
       faker.lorem.sentence(),
       faker.lorem.sentence(),
       faker.random.number()
     );
     mockPosts.push(course);
   }

   service.getCourses().subscribe((courses) => {
     expect(courses.length).toBe(10);
   });

   const req = httpMock.expectOne(apiUrl);
   expect(req.request.method).toBe("GET");
   req.flush(mockPosts);
 });
});

Para ejecutar la prueba, desde una consola puede usar el comando ng test. La Figura 19 muestra la salida esperada.

Figura 19. Resultado de la prueba del servicio

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 cs (los cursos que devolvió el servicio), lo que queremos hacer es actualizar el atributo de la clase.

cs => {this.courses = cs; }

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

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

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

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

Ejecutar la aplicación y resolver el error

Al ejecutar la aplicación nos aparece el siguiente error en la consola (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 definir la dependencia en el archivo package.json. En la Figura 21 vemos esta dependencia en la línea 22, "bootstrap": "^4.4.1" estamos definiendo una versión de Bootstrap compatible (eso significa ^) con la 4.4.1. Para entender mejor las convenciones o semántica de los símbolos en la definición de las versiones pueden ver en el sitio:

https://docs.npmjs.com/files/package.json#dependencies

Figura 21. Contenido del archivo package.json.

Una vez modificado el archivo package.json debemos ejecutar de nuevo:

npm install 

Esto es así sin importar qué se modificó en ese archivo. Si desde VSCode tengo un terminal que está ejecutando ng serve, entonces debo suspender esa ejecución e invocar el npm install (ver Figura 22).

Figura 22. Terminación de la ejecución de ng serve

El resultado es instalar las dependencias nuevas.

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

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. Tomando como referencia el tutorial de pruebas de integración en Angular, se escribirá la siguiente prueba:

import { CourseComponent } from "./course.component";
import { ComponentFixture, async, TestBed } from "@angular/core/testing";
import { HttpClientTestingModule } from "@angular/common/http/testing";

import faker from "faker";
import { Course } from "./course";
import { DebugElement } from "@angular/core";
import { By } from "@angular/platform-browser";

describe("CoursesComponent", () => {
 let component: CourseComponent;
 let fixture: ComponentFixture<CourseComponent>;
 let debug: DebugElement;

 beforeEach(async(() => {
   TestBed.configureTestingModule({
     declarations: [CourseComponent],
     imports: [HttpClientTestingModule],
   }).compileComponents();
 }));

 beforeEach(() => {
   fixture = TestBed.createComponent(CourseComponent);
   component = fixture.componentInstance;
   component.courses = [
     new Course(
       faker.lorem.sentence(),
       faker.lorem.sentence(),
       faker.random.number()
     ),
   ];
   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);
 });

});

En esta prueba se verifica que se haya renderizado una tabla con al menos un nodo como hijo, en este caso, una fila. Para esto se crea manualmente un curso con datos proporcionados por la librería faker.

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.