Mostrar un listado de Héroes
En esta página, ampliaremos la aplicación Tour de Héroes para que muestre un listado de héroes y permita que los usuarios seleccionen un héroe, mostrándose su detalle.
Crear héroes de prueba
Vamos a necesitar algunos héroes que mostrar.
En una versión final, los obtendremos de un servidor remoto, pero por ahora crearemos unos héroes simulados y fingiremos que provienen del servidor.
Crea un fichero llamado mock-heroes.ts
en la carpeta src/app/
. Define una constante HEROES
como un array de diez héroes y expórtala. El fichero debería quedar así.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import { Hero } from './hero'; export const HEROES: Hero[] = [ { id: 11, name: 'Mr. Nice' }, { id: 12, name: 'Narco' }, { id: 13, name: 'Bombasto' }, { id: 14, name: 'Celeritas' }, { id: 15, name: 'Magneta' }, { id: 16, name: 'RubberMan' }, { id: 17, name: 'Dynama' }, { id: 18, name: 'Dr IQ' }, { id: 19, name: 'Magma' }, { id: 20, name: 'Tornado' } ]; |
Mostrando los héroes
Estamos apunto de mostrar un listado de héroes en la parte superior de HeroesComponent
Abre el fichero de la clase HeroesComponent
e importa el HEROES
simulado.
1 |
import { HEROES } from '../mock-heroes'; |
Añade una propiedad heroes
a la clase que expone estos héroes para vincularlos.
1 |
heroes = HEROES; |
Listar héroes con *ngFor
Abre la plantilla de HeroesComponent
y haz los siguientes cambios:
- Añade un
<h2>
en la parte de arriba, - Debajo de él añade una lista HTML (
<ul>
) - Inserta un
<li>
dentro de<ul>
que muestre la propiedadhero
. - Añade clases CSS para los estilos (los añadiremos en el fichero CSS en breve).
Debería parecerse a esto:
1 2 3 4 5 6 |
<h2>My Heroes</h2> <ul class="heroes"> <li> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> </ul> |
Ahora cambia el <li>
por esto:
1 |
<li *ngFor="let hero of heroes"> |
El *ngFor
es la directiva Angular para iterar. Repite el elemento ‘anfitrión’ por cada elemento de la lista.
En este ejemplo:
<li>
es el elemento anfitriónheroes
es la lista de la clasesHeroesComponent
.hero
contiene el elemento héroe actuar para cada iteración dentro la lista.
No olvides el asterisco (*) delante de
ngFor
. Es una parte crítica de la sintaxis.
Después de actualizar el navegador, aparecerá el listado de héroes.
Dando estilo a los héroes
El listado de héroes debería ser atractivo y responder visualmente cuando el usuario pasa el ratón por encima y selecciona y héroe de la lista.
En el tutorial anterior, establecimos los estilos básicos para la aplicación en styles.css
. La hoja de estilos no incluía estilos para el listado de héroes.
Puedes añadir más estilos en styles.css
y aumentar la hoja de estilos cada vez que añades componentes.
O bien puede que prefieras definir estilos privados para cada componente y mantener todo lo que un componente necesita – código, HTML y CSS – en un solo lugar.
Este enfoque facilita la reutilización de los componentes y les proporciona un único aspecto incluso cuando los estilos globales difieren.
Puedes definir estilos privados en el array @Component.styles
o como fichero(s) identificados en el array @Component.styleUrls
.
Cuando el CLI generó HeroesComponent
, creó una hoja de estilos heroes.component.css
vacía para HeroesComponent
y apuntó a ella en @Component.styleUrls
de la siguiente manera:
1 2 3 4 5 |
@Component({ selector: 'app-heroes', templateUrl: './heroes.component.html', styleUrls: ['./heroes.component.css'] }) |
Abre el fichero heroes.component.css
y pega los estilos CSS privados para HeroesComponent
. Los encontrarás en la revisión final de código al final de esta página.
Los estilos y las hojas de estilos identificados en los metadatos de
@Component
están limitados a ese componente en concreto. Los estilos deheroes.component.css
se aplican sólo aHeroesComponent
y no afecta el HTML externo o de cualquier otro componente.
Maestro/Detalle
Cuando el usuario hace clic sobre un héroe en la lista maestra, el componente debería mostrar los detalles del héroe seleccionado al final de la página
En esta sección, escucharemos el evento clic sobre un héroe y actualizaremos los detalles del héroe.
Añade un vínculo sobre el evento clic
Añade un vínculo sobre el evento clic de <li>
así:
1 |
<li *ngFor="let hero of heroes" (click)="onSelect(hero)"> |
Este es un ejemplo de la sintaxis de vinculación de eventos de Angular
El paréntesis que rodea a click
el indica a Angular que escuche los eventos click
sobre el elemento <li>
. Cuando el usuario hace clic en <li>
, Angular ejecuta la expresión onSelect(hero)
.
OnSelect()
es un método de HeroesComponent
que estamos a punto de crear. Angular lo llama pasando el objeto hero
que se muestra en el <li>
donde has hecho clic, el mismo hero
definido previamente en la expresión *ngFor
.
Añade el siguiente método onSelect()
, el cual asigna el héroe sobre el que hemos hecho clic en la plantilla al SelectedHero
del componente.
1 2 3 4 5 |
selectedHero: Hero; onSelect(hero: Hero): void { this.selectedHero = hero; } |
Actualiza la plantilla de detalle
La plantilla todavía hace referencia a la antigua propiedad hero
del componente, la cual ya no existe. Renombra hero
a selectedHero
.
1 2 3 4 5 6 7 |
<h2>{{ selectedHero.name | uppercase }} Details</h2> <div><span>id: </span>{{selectedHero.id}}</div> <div> <label>name: <input [(ngModel)]="selectedHero.name" placeholder="name"> </label> </div> |
Oculta el detalle vacío con *ngIf
Después de que el navegador se actualice, la aplicación deja de funcionar.
Abre el las herramientas de desarrollador del navegador y busca en la consola un mensaje de error como este:
HeroesComponent.html:3 ERROR TypeError: Cannot read property ‘name’ of undefined
Ahora haz clic en uno de los elementos del listado. La aplicación parece que funciona de nuevo. Los héroes aparecen en el listado y los detalles del héroe seleccionado aparecen en el final de la página.
¿Qué ha pasado?
Cuando la aplicación arranca, el selectedHero
vale undefined
(no definido) por diseño.
Las expresión de vinculación de la plantilla referidas a las propiedades de selectedHero
– expresiones como {{selectedHero.name}}
– fallarán porque no hay un héroe seleccionado.
La solución
El componente sólo debería mostrar los detalles del héroe seleccionado si selectedHero
existe.
Envuelve el HTML de los detalles del héroe en un <div>
. Añade la directiva Angular *ngIf
al <div>
y asígnale selectedHero
.
No olvides el asterisco (*) delante de
ngIf
. Es una parte crítica de la sintaxis.
1 2 3 4 5 6 7 8 9 10 11 |
<div *ngIf="selectedHero"> <h2>{{ selectedHero.name | uppercase }} Details</h2> <div><span>id: </span>{{selectedHero.id}}</div> <div> <label>name: <input [(ngModel)]="selectedHero.name" placeholder="name"> </label> </div> </div> |
Después de que el navegador se actualice, el listado de nombres vuelve a aparecer. El área de detalle está vacía. Ha< clic sobre un héroe y sus detalles se mostrarán.
¿Por qué funciona?
Cuando selectedHero
no está definido, ngIf
elimina el detalle del héroe del DOM. No existen entonces vinculaciones de selectedHero
de las que preocuparse.
Cuando el usuario selecciona un héroe, selectedHero
toma un valor y ngIf
pone el detalle del héroe en el DOM.
Dar estilos al héroe seleccionado
Es difícil identificar el héroe seleccionado en el listado cuando todos los elementos li
se ven igual.
Si el usuario hace clic en «Magneta», este héroe debería mostrarse con un color de fondo ligeramente diferente, tal que así:
Dar color al héroe seleccionado es tarea de la clase CSS .selected
en los estilos que añadiste anteriormente. Sólo hay que aplicar la clase -selected
a la etiqueta <li>
cuando el usuario haga clic en ella.
La vinculación de clase de Angular facilita añadir y eliminar una clase CSS de forma condicionada. Tan sólo añade [class.some-css-class]="some-condition"
al elemento al que quieras dar estilos.
Añade el siguiente vínculo [class.selected]
a <li>
en la plantilla de HeroesComponent
.
1 |
[class.selected]="hero === selectedHero" |
Cuando la fila héroe actual es la misma que selectedHero
, Angular le añade la clase CSS selected
. Cuando los dos héroes son diferentes, Angular elimina la clase CSS.
El <li>
terminado queda así:
1 2 3 4 5 |
<li *ngFor="let hero of heroes" [class.selected]="hero === selectedHero" (click)="onSelect(hero)"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> |
Revisión final de código
Tu aplicación debería parecerse a este ejemplo / descarga ejemplo.
Aquí están los ficheros tratados en esta página, incluyendo los estilos de HeroesComponent
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import { Component, OnInit } from '@angular/core'; import { Hero } from '../hero'; import { HEROES } from '../mock-heroes'; @Component({ selector: 'app-heroes', templateUrl: './heroes.component.html', styleUrls: ['./heroes.component.css'] }) export class HeroesComponent implements OnInit { heroes = HEROES; selectedHero: Hero; constructor() { } ngOnInit() { } onSelect(hero: Hero): void { this.selectedHero = hero; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<h2>My Heroes</h2> <ul class="heroes"> <li *ngFor="let hero of heroes" [class.selected]="hero === selectedHero" (click)="onSelect(hero)"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> </ul> <div *ngIf="selectedHero"> <h2>{{ selectedHero.name | uppercase }} Details</h2> <div><span>id: </span>{{selectedHero.id}}</div> <div> <label>name: <input [(ngModel)]="selectedHero.name" placeholder="name"> </label> </div> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
/* HeroesComponent's private CSS styles */ .selected { background-color: #CFD8DC !important; color: white; } .heroes { margin: 0 0 2em 0; list-style-type: none; padding: 0; width: 15em; } .heroes li { cursor: pointer; position: relative; left: 0; background-color: #EEE; margin: .5em; padding: .3em 0; height: 1.6em; border-radius: 4px; } .heroes li.selected:hover { background-color: #BBD8DC !important; color: white; } .heroes li:hover { color: #607D8B; background-color: #DDD; left: .1em; } .heroes .text { position: relative; top: -3px; } .heroes .badge { display: inline-block; font-size: small; color: white; padding: 0.8em 0.7em 0 0.7em; background-color: #607D8B; line-height: 1em; position: relative; left: -1px; top: -4px; height: 1.8em; margin-right: .8em; border-radius: 4px 0 0 4px; } |
Resumen
- La aplicación Tour de Héroes muestra un listado de héroes en una vista Maestro/Detalle.
- El usuario puede seleccionar un héroe y ver sus detalles.
- Hemos usado
*ngFor
para mostrar una lista. - Hemos usado
*ngIf
para incluir o excluir de forma condicionada un bloque HTML. - Podemos activar/desactivar una clase de estilos CSS con un vínculo
class
.
Nota: puedes encontrar el documento original de esta entrada en https://angular.io/tutorial/toh-pt2