frontbookpaso1 - keblato/TutorialesTalleres-Angular GitHub Wiki

Ejemplo Book NG Paso 1

Vamos a ir desarrollando el ejemplo de Book paso por paso introduciendo conceptos nuevos cada vez. Para ejecutar y ver este primer paso, ud debe clonar el proyecto, hacer checkout del paso y luego ejecutarlo como se explicó en ejecutar.

Paso-1 Listar los libros en una Galería, autores y las editoriales en listas simples.
URL: https://github.com/Uniandes-isis2603/front-pregrado201820.git
Release: git checkout -f paso-1a

Los objetivos de este primer paso son mostrar una aplicación conformada por varios módulos, componentes y servicios, utilizar el módulo router de Angular para implementar la navegación en la aplicación e implementar pruebas.

Funcionalidad

En el paso-1 implementamos el requerimiento de desplegar la lista de libros en una galería, la lista de autores en una lista simple y la lista de editoriales en una lista simple. La aplicación es responsive.

Al iniciar la aplicación se despliega una barra de menús con las opciones Books, Authors y Editorials.

Cuando el usuario selecciona Books se despliegan los libros en formato galería donde cada libro es presentado por la imagen de su portada.

Paso1 Galeria books
Figura 1: lista de libros

Cuando el usuario selecciona Authors se despliega la lista de autores.

AutoresPaso1.png
Figura 2: lista de autores

Cuando el usuario selecciona Editorials se despliega la lista de editoriales.

EditorialesPaso1.png
Figura 3: lista de editoriales
Ir al inicio

Diseño global

Nuestra aplicación tiene tres módulos funcionales. En nuestra decisión de diseño, tenemos un módulo por cada concepto diferente de la aplicación (cada recurso): BookModule, AuthorModule y EditorialModule.

Cada uno de los módulos definirá un componente por cada interacción distinta que el usuario necesite realizar (Podría pensarse que hay un componente por caso de uso. Si el caso de uso es complejo, es posible que más de un componente intervenga en su implementación).

El siguiente diagrama presenta los módulos de la aplicación. El principal AppModule que importa los demás: Por un lado los funcionales BookModule, AuthorModule y EditorialModule, luego está el módulo AppRoutingModule que se ocupa de la navegación en la aplicación (explicaremos más adelante) y los módulos de Angular que se requieren para este paso.

diseño global

También es importante ver la vista de desarrollo de la aplicación, es decir, la organización en carpetas y archivos los elementos del proyecto. La siguiente imagen la muestra. La carpeta src/app contiene una carpeta por cada uno de los módulos de la aplicación:

Un Módulo Funcional

Así como vimos en el ejemplo de la lista simple de editoriales, cada módulo funcional define:

  • Sus componentes; en este caso solo está el componente de listar (BookListComponent, AuthorListComponent y EditorialListComponent )
  • Sus servicios: en este caso sólo tenemos un servicio que se ocupa de traer los elementos que se van a mostrar (BookService, AuthorService y EditorialService).
  • El tipo (interface Typescript) del modelo que representa el recurso ( Book, Author y Editorial).

Aclaración: este diagrama no incluye los elementos relacionados con las pruebas de los componentes. Este tema se abordará más adelante.

En el ejemplo LIstar Editoriales está explicado cómo funciona el servicio que trae la colección de editoriales y cómos e hace el despliegue en la vista (template html).

Aquí vamos a explicar los elementos diferentes. Uno de ellos es la galería que despliega los libros.

Ir al inicio

El template de la Galería

En el caso del componente BookListComponent, en el componente se declara el template que se encuentra en el archivo book-list.component.html. La diferencia de esta vista con respecto a la lista de editoriales y a la lista de autores es que se despliegan imágenes de las carátulas de los libros. Para esto utilizamos elementos de HTML 5 y de bootstrap para desplegar las imágenes.

<div class="container-fluid">
    <div class="row">
        <div class="col" *ngFor = "let book of books">
             <figure class="figure"> 
                <img class="img-thumbnail img-fluid" src='{{book.image}}' alt="{{book.name}}" />                                     
                <figcaption class="figure-caption text-center">{{book.name}}</figcaption>
            </figure>

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

El tag figure permite asociar la imagen img con el título debajo de ella.

Ir al inicio

La Navegación

En este ejemplo tenemos en la parte superior de la pantalla la barra de menús. La intención es permitir al usuario navegar sobre los componentes de listar los elementos. El comportamiento que queremos es que cuando el usuario haga clic en Books aparezca la galería de libros, clic en Editorials la lista de editoriales y clic en Authors la lista de los autores.

El código html que construye la barra de menús está definido dentro del archivo app.component.html que corresponde al template definido en el componente AppComponent. El código html de la barra de menús es muy simple:

<ul class="nav nav-tabs">
    <li class="nav-item">
        <a id="booksTag" class="nav-link" routerLink="/books/list">Books</a>
    </li>
    <li class="nav-item">
        <a id="authorsTag" class="nav-link" routerLink="/authors/list">Authors</a>
    </li>
    <li class="nav-item">
        <a id="editorialTag" class="nav-link" routerLink="/editorials/list">Editorials</a>
    </li>
</ul>
<router-outlet></router-outlet>

Note que en los elementos de enlace, es decir los tag a, en vez de tener el atributo href tiene un atributo de Angular llamado routerLink. Hay dos cosas que necesitamos entender:

  • Qué es y dónde se define el valor del atributo
  • Dónde se muestra lo que se va a desplegar. Para este punto, la respuesta es que se despliega remplazando la línea <router-outlet></router-outlet> que se encuentra al final del archivo en el template. En pasos posteriores veremos otras formas de definir dónde desplegar.

Las rutas

AppRoutingModule es el módulo donde definimos los enrutamientos o la navegación de la aplicación y funciona gracias a los paquetes Routes y RouterModule de la librería @angular/router.

Los valores que puede tomar el atributo routerLink corresponden a rutas de navegación que debemos definir. EN este ejemplo, las hemos definido en el módulo AppRoutingModule. Como se puede observar en el código, definimos un arreglo de rutas de navegación, llamado routes y cuyo tipo es Routes donde cada ruta define un path y un componente que es el que contiene el template que se mostrará cuando el routerLink tenga por valor el path correspondiente.

Note que podemos hacer una jerarquía de paths. Significa que si queremos que se despliegue el template definido en el componente BookListComponent, el path será: books/list.

Para efectos de organización, el router de esta aplicación define tres paths principales (books, authors y editorials) que tienen children, es decir rutas que continúan a partir de esa raíz (ej. books/list).

import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {RouterModule, Routes} from '@angular/router';
import {BookListComponent} from '../book/book-list/book-list.component';
import {AuthorListComponent} from '../author/author-list/author-list.component';
import {EditorialListComponent} from '../editorial/editorial-list/editorial-list.component';


const routes: Routes = [
    
    {
        path: 'books',
        children: [
            {
                path: 'list',
                component: BookListComponent
            }
        ]
    },
    {
        path: 'authors',
        children: [
            {
                path: 'list',
                component: AuthorListComponent
            }
        ]
    },
    {
        path: 'editorials',
        children: [
            {
                path: 'list',
                component: EditorialListComponent
            }
        ]
    }
];

@NgModule({
    imports: [
        CommonModule,
        RouterModule.forRoot(routes)
    ],
    exports: [RouterModule],
    declarations: []
})
export class AppRoutingModule {
    
}
Ver Video
Ir al inicio

Pruebas

Angular cuenta con paquetes de pruebas que se encuentran en la librería @angular/core/testing. Las pruebas que se pueden realizar con esos paquetes varían desde pruebas unitarias, hasta pruebas más interesantes de interfaz.

La arquitectura de estas pruebas se muestra en un diagrama simplificado a continuación (no incluye las pruebas del componente EditorialListComponent):

Paso1-PruebasBook.png
Figura 5: diagrama simplificado de pruebas

Al crear un nuevo componente por medio del CLI de Angular, este le crea a cada componente su spec de pruebas correspondiente (ej. book-list.component.spec.ts).

En estos archivos .spec.ts se definen las pruebas para el componente que le corresponde.

La estructura básica del archivo comienza con los imports de los elementos necesarios para las pruebas. Todas estas están escritas dentro de un "contenedor" describe.

Antes de comenzar a describir las pruebas, es necesario definir ciertas acciones a realizar antes de cada prueba. Esto se realiza en los métodos beforeEach.

A continuación se muestra la implementación del describe y el beforeEach en book-list.component.spec.ts:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AppRoutingModule } from '../../routing-module/app-routing.module';
import { APP_BASE_HREF } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';

import { AppModule } from '../../app.module';

import { BookListComponent } from './book-list.component';
import {BookService} from '../book.service';
import { Book } from '../book';

describe('BookListComponent', () => {
    let component: BookListComponent;
    let fixture: ComponentFixture<BookListComponent>;
    const books: Book[] = require('../../../assets/books.json');
    
    beforeEach(async(() => {
        TestBed.configureTestingModule({
            imports: [ AppRoutingModule, HttpClientModule, AppModule ],
            declarations: [ ],
            providers: [{provide: APP_BASE_HREF, useValue: ''}, BookService ]
        })
        .compileComponents();
    }));
    
    beforeEach(() => {
        fixture = TestBed.createComponent(BookListComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
    });

El primer beforeEach se encarga de hacer las declaraciones necesarias para que las pruebas compilen.

El segundo define que en este caso la variable fixture crea el componente. La instancia de ese componente creado se guarda en component y es sobre esa variable que luego se hacen las pruebas.

En este ejemplo se definieron 3 pruebas para cada componente.

En el caso de la lista de libros:

 it('should create', () => {
        expect(component).toBeTruthy();
    });
    
    
    it('should have a list of books', () => {
        component.books = books;
        expect(component.books.length).toEqual(books.length);
    });

    it('a book should be a book (first and last)', () => {
        component.books = books;
        //revisar todos los libros
        expect(component.books[0].name).toEqual(books[0].name);
        expect(component.books[books.length - 1].name).toEqual(books[books.length - 1].name);
    });
});

Cada prueba se escribe dentro de un it, el String que corresponde al primer parámetro es el título de la prueba. El segundo es la función que evalúa la prueba.

La primera prueba corresponde a la básica que verifica que el componente se puede crear de manera correcta.

La segunda verifica que la lista de libros del componente sea tal como se espera. La lista es comparada con el json local (en la carpeta assets) que contiene la lista completa de libros en tamaño, y en la verificación de algunos de los objetos en la tercera prueba.

En este primer paso 1a, la lista es el mismo archivo así que la prueba es obvia, pero esto cambiará a partir del paso 1b que conecta el front con el back.

Ejecución de las pruebas

Las pruebas se ejecutan con el comando/script ng test.

Los resultados de estas se despliegan en localhost:9876.

Pensando en la integración continua, el proyecto se configuró para que utilice PhantomJS (un navegador headless) que muestra el resultado de las pruebas en consola. De esta manera, las pruebas se pueden ejecutar por ejemplo en Jenkins.

PruebasBookListComponent.png
Figura 6: ejemplo de la ejecución de las pruebas utilizando PhantomJs
Ir al inicio
⚠️ **GitHub.com Fallback** ⚠️