-
Notifications
You must be signed in to change notification settings - Fork 15
frontbookpaso1
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, autores y editoriales en distintos tipos de Galería/listas |
---|---|
URL: | https://github.com/Uniandes-isis2603/frontstepbystep.git |
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.
En este paso implementamos el requerimiento de desplegar la lista de libros en una galería, la lista de autores en cierto tipo de lista y la lista de editoriales en cierto tipo de lista. La aplicación es responsive.
Al iniciar la aplicación se despliega una barra de navegación 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.
Figura 1: lista de libros |
Cuando el usuario selecciona Authors
se despliega la lista de autores.
Figura 2: lista de autores |
Cuando el usuario selecciona Editorials
se despliega la lista de editoriales.
Figura 3: lista de editoriales |
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).
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:
Se debe tener en cuenta que cada módulo funcional define:
- Sus componentes; en este caso solo está el componente de listar (
BookListComponent
,AuthorListComponent
yEditorialListComponent
) - Sus servicios: en este caso sólo tenemos un servicio que se ocupa de traer los elementos que se van a mostrar (
BookService
,AuthorService
yEditorialService
). - El tipo (
interface
Typescript) del modelo que representa el recurso (Book
,Author
yEditorial
).
En el caso del componente BookListComponent
, en el componente se declara el template que se encuentra en el archivo book-list.component.html
. Para desplegar las carátulas de los libros utilizamos elementos de HTML 5 y de bootstrap para desplegar las imágenes.
<div class="container-fluid">
<div class="row">
<div *ngFor="let book of books" class="col mb-2">
<div class="card p-2" style="width: 15rem; height: 33rem">
<img
class="card-img-top"
src="{{ book.image }}"
alt="{{ book.name }}"
/>
<div class="card-body">
<h5 class="card-title">{{ book.name }}</h5>
<p class="card-text">[{{ book.editorial.name }}]</p>
</div>
</div>
</div>
</div>
</div>
Nótese que se emplea card
para desplegar la información de los libros.
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.
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 |
---|
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 de interfaz.
Al crear un nuevo componente o un nuevo servicio por medio del CLI de Angular, se asocia cada componente su spec de pruebas correspondiente (ej. book-list.component.spec.ts o book.service.spec.ts
).
En estos archivos .spec.ts
se definen las pruebas para el componente o servicio 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 de la prueba para el servicio book, la cual se define en el archivo book.service.spec.ts
:
import { TestBed, async, inject, getTestBed } from '@angular/core/testing';
import { BookService } from './book.service';
import {
HttpTestingController,
HttpClientTestingModule,
} from '@angular/common/http/testing';
import faker from 'faker';
import { Book } from './book';
import { environment } from '../../environments/environment';
describe('Service: Book', () => {
let injector: TestBed;
let service: BookService;
let httpMock: HttpTestingController;
let apiUrl = environment.apiURL + '/books';
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [BookService],
});
injector = getTestBed();
service = injector.get(BookService);
httpMock = injector.get(HttpTestingController);
});
afterEach(() => {
httpMock.verify();
});
it('getPost() should return 10 records', () => {
let mockPosts: Book[] = [];
for (let i = 1; i < 11; i++) {
let book = new Book(
i,
faker.lorem.sentence(),
faker.random.number(),
faker.lorem.sentence(),
faker.image.imageUrl(),
faker.date.past(),
null
);
mockPosts.push(book);
}
service.getBooks().subscribe((books) => {
expect(books.length).toBe(10);
});
const req = httpMock.expectOne(apiUrl);
expect(req.request.method).toBe('GET');
req.flush(mockPosts);
});
});
Las pruebas se ejecutan con el comando/script ng test
.
Los resultados de estas se despliegan en localhost:9876
.