Este tutorial lo guía, usando VSCode, en la construcción de una aplicación Angular compuesta del módulo principal y de un módulo llamado BookModule el cual declara un componente BokkListarComponent para desplegar el catálogo de libros que está en la aplicación del curso backstepbystep.

Los pasos que siguen son:

El resultado final del tutorial es una aplicación que despliega la siguiente lista (Figura 1):

Figura 1

Antes de realizar este tutorial Ud. ya desarrolló el tutorial Lista de cursos. Allí se explican varios conceptos que no se retoman aquí.

En particular Ud. debe:

  1. Tener instalado el ambiente: VSCode, TS, Angular
  2. Saber cómo se instala Bootstrap en el proyecto
  3. Saber cómo se crean módulos, componentes, servicios utilizando el angular-cli en VSCode.

Para ejecutar el resultado final de este taller Ud. debe tener en ejecución sobre payara, el proyecto backstepbystep. No olvide inicializar la base de datos y ejecutar el sql que inserta los datos.

Paso 1: Crear el proyecto

Cree una aplicación Angular que se llame, por ejemplo, book, siguiendo las instrucciones que se encuentran aquí.

Abra su aplicación en VSCode borre el contenido del archivo app.component.html.

Paso 2: Crear un nuevo módulo

Para crear el nuevo módulo utilizamos la aplicación angular-cli que está integrada dentro de VSCode.

Para esto, vaya a la carpeta src/app, clic derecho, Generate Module. El nombre del nuevo módulo es book.

Paso 2: Borre el componente por defecto

Al crear el nuevo módulo book, se generó un componente por defecto BookComponent.

Para eliminar ese componente que no vamos a utilizar debe borrar los siguientes archivos:

book.component.ts

book.component.css

book.component.html

Borre referencias a este componente en el archivo book.module.ts que debe quedar así:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
  imports: [
    CommonModule
  ],
  declarations: []
})
export class BookModule { }

Paso 3: Incluir el nuevo módulo en el principal

Para que la aplicación pueda utilizar el nuevo módulo, este se debe importar en el módulo principal AppModule (archivo app.module.ts).

Para importar en el módulo principal el módulo de BookModule se debe realizar dos cosas:

  1. Ir al decorador e incluir en el arreglo del atributo imports el nombre del módulo, es decir, de la clase BookModule

import { BookModule } from './book/book.module';

Paso 4: Crear el componente de listar

Vaya sobre la carpeta del módulo src/app/book, clic derecho Generate Component y escriba por nombre book-listar.

Dentro de la carpeta src/app/book se debió crear una nueva carpeta para el componente book-listar:

Paso 5: Declarar y exportar el componente en el módulo

El resultado es:

src/app/book/book.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BookListarComponent } from './book-listar/book-listar.component';
@NgModule({
  imports: [
    CommonModule
  ],
  declarations: [BookListarComponent],
  exports: [BookListarComponent],
})
export class BookModule { }

Paso 6: Invocar el componente

Para invocar el componente de los libros dentro del HTML del componente principal, tenemos que seguir los siguientes pasos:

  1. Buscamos el nombre del selector de BookListarComponent. Vamos al archivo book-listar-component.ts y en del decorador del componente buscamos el valor del atributo selector.
  2. Lo utilizamos en app.component.html. Escríbalo en la segunda línea.

Volvemos a ejecutar la aplicación y obtenemos. Significa que ya nuestra aplicación está invocando al componente de listar los libros y podemos seguir desarrollandolo.

Paso 1: Definir la estructura del modelo

El modelo se refiere a la información que será desplegada o ingresada en o por la vista del componente y que será mantenida por el componente de forma sincronizada (binding). Significa que si el modelo cambia la vista se actualiza de forma automática.

En nuestro ejemplo, debemos definir las clases de la estructura de los libros. El diseño de esta estructura, obviamente, depende de la estructura de los objetos json que nos va a retornar el API Rest que estamos utilizando. En nuestro caso, los objetos que retorna el API REST al solicitar Get books es:

En este ejemplo, solo vamos a representar la representación básica de Book. Es decir, las clases que están encerradas en la línea amarilla: Book y Editorial.

Crear la clase Book

La clase Book la creamos dentro de la carpeta del módulo book. Esta clase define los atributos y sus métodos getters.

src/app/book/book.ts

import { Editorial } from '../editorial/editorial';
export class Book {

  constructor(private idA: number, private nameA: string, private isbnA: string,
              private descriptionA: string, private imageA: string, private publishingdateA: any,
              private editorialA: Editorial) { }

  get id(): number { return this.idA; }
  get name(): string { return this.nameA; }
  get isbn(): string { return this.isbnA; }
  get description(): string { return this.descriptionA; }
  get image(): string { return this.imageA; }
  get publishingdate(): any { return this.publishingdateA; }
  get editorial(): Editorial { return this.editorialA; }

}

Crear la clase Editorial

Para crear la clase Editorial, de forma ordenada, debemos crear el módulo EditorialModule, dado que en nuestro ejemplo este es un módulo funcional distinto.

Creamos el módulo, utilizando el angular-cli, debe quedar en su propia carpeta así:

No hay que olvidar asociar el módulo EditorialModule en el módulo principal AppModule quien lo debe importar:

...
import { EditorialModule } from './editorial/editorial.module';

@NgModule({
 ...
  imports: [
   ...
    EditorialModule
  ],
 ...

Dentro del módulo creamos la clase Editorial, que tiene dos atributos y los correspondientes getters:

export class Editorial {

  constructor(private idA: number, private nameA: string) {
  }
  get id(): number { return this.idA; }
  get name(): string { return this.nameA; }
}

Paso 2: Asociar el modelo con el componente

Ahora que tenemos la clase que representa los libros, podemos declarar, dentro de la clase del componente BookListarComponent, un arreglo para los libros:

private books: Array<Book>; 

Nos aparece que Book no está definido, entonces debemos importarlo:

import { Component, OnInit } from '@angular/core';
import { Book } from '../book';
@Component({
  selector: 'app-book-listar',
  templateUrl: './book-listar.component.html',
  styleUrls: ['./book-listar.component.css']
})
export class BookListarComponent implements OnInit {
  constructor() { }
  private books: Array<Book>;
  ngOnInit() {
  }
}

Paso 1: Crear la clase del servicio

Vamos a crear la clase del servicio desde la carpeta book. De nuevo usamos angular-cli y seleccionado Generate Service. Como nombre, solo escribimos book ya que angular-cli completa con la palabra Service.

El archivo generado resultado es:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class BookService {
constructor() { }
}

En BookService creamos un atributo privado http de tipo HttpClient de Angular.

Inyectamos ese servicio declarando en el constructor e importando el archivo de la clase correspondiente:

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

Antes de seguir, vamos al módulo principal AppModule e importamos (en el atributo imports del decorador del módulo, HttpClientModule para que BookServicio pueda usar el HttpClient. El módulo debe quedar de la siguiente forma:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';
import { BookModule } from './book/book.module';
import { EditorialModule } from './editorial/editorial.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BookModule,
    EditorialModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Paso 2: Configurar la URL del back-end

Para esto se necesita conocer la url donde está el servidor que provee los libros (el back-end).

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:

Definimos la url del back-end, suponiendo que el back-end está en el localhost y que se ejecuta en el 8080:

const host: 'localhost';
const port: '8080';
const appName: 'frontstepbystep-api';
const rootApi: 'api';

export const environment = {
  production: false,
baseUrl:`http://${host}:${port}/${appName}/${rootApi}/`
};

Para facilitar la ejecución de este ejemplo, tenemos el back-end ejecutándose en una IP de producción. Entonces los valores son:

const host = '157.253.238.75';
const port = '8084';
const appName = 'frontstepbystep-api';
const rootApi = 'api';

export const environment = {
  production: false,
  baseUrl: `http://${host}:${port}/${appName}/${rootApi}/`
};

Paso 3: Definición de la función http get en el servicio

En la clase del servicio, declaramos una función getBooks() que va a utilizar el servicio http para invocar el http.get. Como ya explicamos, estas funciones de http retornan objetos Observable. Entonces la declaración completa de la función es:

getBooks(): Observable<Book[]> {
    return this.http.get<Book[]>(this.apiUrl);
  }

Para completar este código debemos importar el archivo de Observable y el archivo de Book.

El código completo del servicio es el siguiente:

/src/app/book/book.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Book } from './book';
import { environment } from '../../environments/environment';
 

@Injectable({
  providedIn: 'root'
})
export class BookService {
  private apiUrl = environment.baseUrl + 'books';
  constructor(private http: HttpClient) { }
  getBooks(): Observable<Book[]> {
    return this.http.get<Book[]>(this.apiUrl);
  }
}

Nuestro componente debe ahora llamar la función creada en el servicio. Tenemos que hacer varias cosas:

Para poder usar el servicio en el componente necesitamos declararlo en el constructor e importar el archivo, el fragmento de código donde está esta declaración es:

...
import { BookService } from '../book.service';

@Component({
  selector: 'app-book-listar',
  templateUrl: './book-listar.component.html',
  styleUrls: ['./book-listar.component.css']
})
export class BookListarComponent implements OnInit {
  constructor(private bookService: BookService) { }
...
}

Debemos declarar el método del componente getBooks() así:

getBooks(): void {
    this.bookService.getBooks()
      .subscribe(books => {
        this.books = books;
      });
  }
ngOnInit() {
    this.getBooks();
  }

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

/src/app/book/book.component.ts

import { Component, OnInit } from '@angular/core';
import { Book } from '../book';
import { BookService } from '../book.service';

@Component({
  selector: 'app-book-listar',
  templateUrl: './book-listar.component.html',
  styleUrls: ['./book-listar.component.css']
})
export class BookListarComponent implements OnInit {
  constructor(private bookService: BookService) { }

  private books: Array<Book>;

  getBooks(): void {
    this.bookService.getBooks()
      .subscribe(books => {
        this.books = books;
      });
  }
  ngOnInit() {
    this.getBooks();
  }

}

Como ya hemos explicado, la vista es el HTML asociado con el componente. El objetivo es desplegar la lista de libros en una galería con las imágenes de las portadas de los libros.

Vamos a hacer primero un despliegue muy básico de las imágenes de los libros utilizando la grilla de Bootstrap. Cada imagen irá en una columna y debajo de la imagen va el nombre del libro. Las imágenes son responsive y todo está dentro de un container-fluid.

Antes de hacer la vista es importante que instale la dependencia de bootstrap (npm install bootstrap), y haber incluido en el archivo angular.json, en el atributo styles -línea 22-, el siguiente valor: "node_modules/bootstrap/dist/css/bootstrap.min.css"

Ahora veamos el código de la vista, empezando solo con el HTML básico y el uso de Bootstrap. En el siguiente código HTML, tenemos un container-fluid, una fila y una columna.

<div class="container-fluid">
  <div class="row">
      <div class="col">

      </div>
  </div>
</div>

Queremos crear una columna por cada libro que se encuentra en el arreglo books. Entonces, necesitamos definir un ciclo sobre la etiqueta div que define la columna para que itere sobre books y cree una nueva columna en cada iteración, vamos a escribir el nombre del libro en cada columna utilizando la expresión {{b.name}}:

<div class="container-fluid">
  <div class="row p-5">
      <div class="col border" *ngFor= "let b of books">
         {{b.name}}
      </div>
  </div>
</div>

Repasemos la sintaxis para definir un ciclo dentro del HTML utilizando las directivas de Angular:

*ngFor= "let b of books"

La directiva *ngFor es un atributo que se define en la etiqueta donde queremos iniciar el ciclo. El ciclo se termina donde se termina esa etiqueta. El valor del atributo debe definir:

Ahora vamos a incluir la imagen del libro. Hay muchas formas de hacerlo, utilizando etiquetas cono figure o como card.

Vamos a crear una etiqueta figure al interior de cada columna. Dentro de figure tenemos la imagen, etiqueta img, y el título de la imagen, etiqueta figcaption, que en este caso es el nombre del libro. Hemos además, agregado el nombre de la editorial.

<div class="container-fluid">
  <div class="row p-5">
      <div class="col" *ngFor="let b of books">
          <figure class="figure">
              <img class="img-fluid" src='{{b.image}}'/>
              <figcaption class="title-caption text-center">{{b.name}}. [{{b.editorial.name}}]</figcaption>
          </figure>
      </div>
  </div>
</div>

El nuevo despliegue es::