Skip to content
Nicolás Potes García edited this page Apr 8, 2022 · 1 revision

Paso 3

Los requerimientos de este nuevo paso son los siguientes:

  1. Ver la lista de autores y su detalle en la misma página
  2. 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:

  1. Una nueva barra de navegación
  2. Un nuevo footer

Ver la lista de autores y su detalle en la misma página

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:

  1. Quién "ensambla" los dos componentes para que el usuario los vea al mismo tiempo
  2. Cómo se comunica el componente maestro, cuando selecciona el usuario un elemento, con el detalle para que sea ese el elemento que despliega.

Ensamble de los componentes

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.

Pasar al detalle el elemento seleccionado como parámetro

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.

Ver las reviews de un libro en un componente que se puede colapsar

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.

Template del detalle de reviews

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>

Paso del valor de los reviews al componente es el componente que muestra el detalle del libro

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.