-
Notifications
You must be signed in to change notification settings - Fork 15
paso3
Los requerimientos de este nuevo paso son los siguientes:
- Ver la lista de autores y su detalle en la misma página
- Ver las reviews de un libro en un componente que se puede colapsar
Además de eso, se explicarán ciertos cambios en la interfaz:
- Una nueva barra de navegación
- Un nuevo
footer
El patrón de diseño Maestro/Detalle es muy frecuente en las aplicaciones Single-Page. Se puede implementar de muchas formas distintas. Para este ejemplo, hemos decidido una vista a la izquierda con el Maestro(la lista) y una vista a la derecha con el detalle. Son dos componentes distintos: en la izquierda es AuthorListComponent
y a la derecha es AuthorDetailComponent
.
Las decisiones que debemos tomar son:
- Quién "ensambla" los dos componentes para que el usuario los vea al mismo tiempo
- Cómo se comunica el componente maestro, cuando selecciona el usuario un elemento, con el detalle para que sea ese el elemento que despliega.
Podemos tener un componente que sólo se ocupe de ensamblar componentes. Sin embargo, en este ejemplo, el componente de la lista, el maestro, ensambla al detalle. Esto se hace en el template del componente de la lista.
Hemos cambiado el html de author-list.component.html
para incluir una nueva columna col
que contiene el selector del componente detalle app-author-detail
:
...
<div class="col-8">
<div *ngIf="selectedAuthor">
<app-author-detail [authorDetail]="selectedAuthor"></app-author-detail>
</div>
</div>
...
Este selector está recibiendo un parámetro llamado authorDetail
cuyo valor es selectedAuthor
, ya vamos a ver como sacamos ese valor. La directiva angular *ngIf="selectedAuthor"
significa que cuando no se ha seleccionado nada, no se muestra el detalle.
En el mismo html de author-list.component.html
en la parte donde se muestra la lista de autores, agregamos la directiva (click)
para obtener el elemento seleccionado:
...
<div *ngFor="let author of authors" class="book-card">
<div class="card p-2 mb-2" style="width: 12rem; height: 23rem">
<a
class="card-block text-decoration-none"
(click)="selectAuthor(author)"
>
<img
class="card-img-top"
src="{{ author.image }}"
alt="{{ author.name }}"
/>
<div class="card-body black-text">
<h5 class="card-title">{{ author.name }}</h5>
<p class="card-text">
{{ author.birthDate }}
</p>
</div>
</a>
</div>
</div>
...
Esta instrucción: (click)="selectAuthor(author)"
significa que cuando se haga clic sobre el elemento se invoca la función selectAuthor
a la que le estamos pasando de argumento el author
. Esta función está definida en el componente al que corresponde este template: AuthorListComponent
. Allí encontramos la definición de la función selectAuthor
:
...
export class AuthorListComponent implements OnInit {
...
author_id: number;
selectedAuthor : Author;
selectAuthor(author: AuthorDetail){
this.selectedAuthor = author;
this.selected = true;
}
...
getAuthorDetail(): void {
this.authorService.getAuthorDetail(this.author_id)
.subscribe(selectedAuthor => {
this.selectedAuthor = selectedAuthor
});
}
...
}
La función selectAuthor
invoca getAuthorDetail()
quien a su vez, a través del servicio de autor, llama por ejemplo, si el valor del id
fuera 123
a:
Get /authors/123
El valor resultado del llamado lo deja en el objeto this.selectedAuthor
. Este objeto es el que se pasa de parámetro en la directiva del detalle que explicamos antes.
Para este requerimiento hemos decidido crear un nuevo componente con la responsabilidad de mostrar los reviews
de un libro en un componente que se puede colapsar. El nuevo componente se llama book-reviews
y declara en la anotación del selector app-book-reviews
y el template asociado está en book-review.component.html
.
import { Component, OnInit, Input, } from '@angular/core';
import { BookService } from '../book.service';
import { Review } from '../review';
@Component({
selector: 'app-book-reviews',
templateUrl: './book-review.component.html',
})
export class BookReviewComponent implements OnInit {
@Input() bookReviews : Review [];
public isCollapsed = true;
ngOnInit(){
}
}
El componente BookReviewComponent
recibe de entrada la lista de reviews que va a desplegar @Input() bookReviews : Review []
. Para esto definimos la variable bookReviews
cuyo tipo es un arreglo de tipo Review
. Este tipo también fue definido en este paso en el archivo review.ts
.
Con respecto a la variable bookReviews
es importante entender quién la usa y quién le da los valores:
El template que muestra los reviews hace uso de la variable bookReviews
. EL template se muestra en el siguiente código html. La primera parte define un botón que el usuario deberá oprimir para ver los reviews. Este botón tiene definido que cuando el usuario haga clic, se cambie el valor del atributo isCollapsed
de true
a false
o viceversa. Lo demás permite cambiar el color del botón para indicar si está o no colapsado.
La segunda parte del template contiene la información de la tabla con los reviews. Esta dentro de un componente llamado
ngbCollapse
que es quien colapsa el texto. Más detalles sobre este componente de bootstrap aquí. Luego está la iteración sobre los reviews
en las filas de la tabla.
<p>
<button type="button" class="btn btn-outline-primary" (click)="isCollapsed = !isCollapsed"
[attr.aria-expanded]="!isCollapsed" aria-controls="collapseExample">
Reviews of the book
</button>
</p>
<div id="collapseExample" [ngbCollapse]="isCollapsed">
<table class="table table-striped">
<tbody>
<tr *ngFor="let review of bookReviews">
<td><span id="book-reviewed"><b>{{review.book.name}}</b></span><span>: </span><span id="review-description">{{review.description}}</span></td>
</tr>
</tbody>
</table>
</div>
Quien invoca el componente de detalle de los reviews es el template asociado con el componente que muestra el detalle de los libros. A continuación el fragmento de este template que contiene la invocación al detalle de los reviews. En este caso, el libro que se está mostrando contiene ya los reviews porque así lo definimos en la clase BookDetail
. Cuando hicimos el Http get books/123
(del libro ejemplo 123
) trajimos también los reviews asociados con el libro 123
.
<div *ngIf="bookDetail.reviews.length > 0">
<app-book-reviews [bookReviews]="bookDetail.reviews"></app-book-reviews>
</div>
Para llamar al componente detalle usamos su selector app-book-reviews
y para pasarle el valor ala variable @Input bookReviews
debemos usar la sintaxis: [bookReviews]="bookDetail.reviews" donde bookDetail.reviews
contiene el arreglo de reviews del libro que estamos mostrando.