Componentes Maestro/Detalle
Por el momento, HeroesComponent
muestra tanto la lista de héroes como los detalles del héroe seleccionado.
Mantener todas las características en un sólo componente a medida que la aplicación crece no sería fácil de mantener. Es mejor dividir los componentes grandes en sub-componentes de menor tamaño, cada uno especializado en una tarea específica.
En esta página daremos el primer paso en esa dirección moviendo los detalles del héroe un componente separado y reutilizable, HeroDetailsComponent
.
HeroesComponent
tan sólo presentará un listado de héroes.HeroDetailsComponent
mostrará los detalles del héroe seleccionado.
Crea HeroDetailsComponent
Usa el CLI de Angular para generar un nuevo componente llamado hero-detail
.
1 <span class="pln">ng generate component hero</span><span class="pun">-</span><span class="pln">detail</span>
Este comando genera los ficheros de HeroDetailComponent
y declara el componente en AppModule
.
Escribe la plantilla
Corta el HTML del detalle del héroe al final de la plantilla de HeroesComponent
y pégalo en la plantilla generada de HeroDetailComponent
.
El HTML copiado hace referencia a selectedHero
. El nuevo HeroDetailComponent
puede mostrar cualquier héroe, no sólo el héroe seleccionado. Así pues, reemplaza «selectedHero» por «hero» en toda la plantilla.
1 2 3 4 5 6 7 8 9 10 11 |
<div *ngIf="hero"> <h2>{{ hero.name | uppercase }} Details</h2> <div><span>id: </span>{{hero.id}}</div> <div> <label>name: <input [(ngModel)]="hero.name" placeholder="name"/> </label> </div> </div> |
Añade la propiedad @Input() hero
La plantilla de HeroDetailComponent
está vinculada a la propiedad hero
del componente, la cual es del tipo Hero
.
Abre el fichero de clase HeroDetailComponent
e importa el símbolo Hero
.
1 |
import { Hero } from '../hero'; |
La propiedad hero
tiene que ser una propiedad de entrada (Input), anotada con el decorador @Input()
, porque el componente externo HeroesComponent
se vinculará a ella de este modo.
1 |
<app-hero-detail [hero]="selectedHero"></app-hero-detail> |
Modifica la sentencia de importación de @angular/core
para incluir el símbolo Input
.
1 |
import { Component, OnInit, Input } from '@angular/core'; |
Añade una propiedad hero
, precedida por el decorador @Input()
.
1 |
@Input() hero: Hero; |
Este es el único cambio que tenemos que hacer en la clase HeroDetailComponent
. No hay más propiedades. No hay lógica de presentación. Este componente tan sólo recibe un objeto héroe a través de la propiedad hero
y lo muestra.
Muestra HeroDetailsComponent
HeroesComponent
todavía es una vista maestro/detalle.
Antes mostraba los detalles del héroe por sí misma, antes de que cortásemos ese trozo de la plantilla. Ahora delegará en HeroDetailComponent
.
Los dos componentes tendrán una relación padre/hijo. El padre HeroesComponent
controlará al hijo HeroDetailComponent
enviando un nuevo héroe para mostrar cuando el usuario seleccione un héroe de la lista.
No vamos a cambiar la clase HeroesComponent
pero sí cambiaremos la plantilla.
Actualiza la plantilla HeroesComponent
El selector de HeroDetailComponent
es 'app-hero-detail'
. Añade un elemento <app-hero-detail>
cerca del final de la plantilla de HeroesComponent
, donde antes estaba el detalle del héroe.
1 |
<app-hero-detail [hero]="selectedHero"></app-hero-detail> |
[hero]="selectedHero"
es una vinculación de propiedad de Angular.
Es un vínculo de datos de un sentido desde la propiedad selectedHero
de HeroesComponent
hasta la propiedad hero
del elemento de destino, el cual se mapea con la propiedad hero
de HeroDetailComponent
.
Ahora cuando el usuario hace clic en un héroe de la lista, selectedHero
cambia. Cuando selectedHero
cambia, la vínculación de propiedad actualiza hero
y HeroDetailComponent
muestra el nuevo héroe.
La plantilla HeroesComponent
revisada debería quedar así:
1 2 3 4 5 6 7 8 9 10 11 |
<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> <app-hero-detail [hero]="selectedHero"></app-hero-detail> |
El navegador se actualiza y la aplicación funciona de nuevo como antes.
¿Qué ha cambiado?
Como antes, cuando un usuario hace clic en el nombre de un héroe, sus detalles aparecen bajo la lista de héroes. Ahora HeroDetailComponent
es el que muestra estos datos en lugar de HeroesComponent
.
Refactorizar HeroesComponent
en dos componentes tiene sus ventajas, tanto ahora como en el futuro:
-
- Hemos simplificado
HeroesComponent
reduciendo sus responsabilidades. - Podemos evolucionar
HeroDetailComponent
en un editor de héroes sin tener que tocar el padreHeroesComponent
- Podemos evolucionar
HeroesComponent
sin tocar la vista de detalles del héroe. - Podemos reutilizar
HeroDetailComponent
en la plantilla de un futuro componente.
- Hemos simplificado
Revisión final de código
Aquí está el código de los ficheros tratados en esta página y tu aplicación debería parecerse a este ejemplo / descarga ejemplo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import { Component, OnInit, Input } from '@angular/core'; import { Hero } from '../hero'; @Component({ selector: 'app-hero-detail', templateUrl: './hero-detail.component.html', styleUrls: ['./hero-detail.component.css'] }) export class HeroDetailComponent implements OnInit { @Input() hero: Hero; constructor() { } ngOnInit() { } } |
1 2 3 4 5 6 7 8 9 10 11 |
<div *ngIf="hero"> <h2>{{ hero.name | uppercase }} Details</h2> <div><span>id: </span>{{hero.id}}</div> <div> <label>name: <input [(ngModel)]="hero.name" placeholder="name"/> </label> </div> </div> |
1 2 3 4 5 6 7 8 9 10 11 |
<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> <app-hero-detail [hero]="selectedHero"></app-hero-detail> |
Resumen
-
-
- Hemos creado un
HeroDetailComponent
separado y reusable. - Hemos usado el vínculo de propiedad para dar control al padre
HeroesComponent
sobre el hijoHeroDetailComponent
. - Hemos usado el decorador @Input para que la propiedad
hero
este disponible para vincularse con el componente externoHeroesComponent
.
- Hemos creado un
-
Nota: puedes encontrar el documento original de esta entrada en https://angular.io/tutorial/toh-pt3