Estás viendo la documentación de Angular
Multiples componentes
Nuestra app está creciendo. A medida que aparecen más casos de uso, resulta necesario reutilizar componentes, hacer que estos puedan pasarse datos entre si, y crear activos más reutilizables. Vamos a separar nuestra lista de heroes del detalle y hacer que el componente del detalle sea reutilizable.
Puedes ejecutar el Ejemplo en vivo para esta parte.
A donde habíamos quedado
Antes de continuar con el Tour de los heroes, vamos a verificar que tengamos la siguiente estructura. Si no, vamos a tener que volver atrás y seguir los capítulos anteriores.
angular-tour-of-héroes
|-> src
|-> app
|-> app.component.ts
|-> app.module.ts
|-> main.ts
|-> index.html
|-> styles.css
|-> systemjs.config.js
|-> tsconfig.json
|-> node_modules ...
|-> package.json
Manten la app transpilando y ejecutando
Vamos a comenzar con el compilador TypeScript, hacer que observe los cambios y arrancar nuestro servidor. Lo hacemos tipeando
npm start
Este comando ejecuta el compilador en modo observador, inicia el servidor, lanza la aplicación en un navegador, y mantiene la app ejecutandose mientras continuamos la construcción del Tour de los Héroes.
Haciendo un componente para el detalle del héroe
Nuestra lista de heroes y detalle del héroe están en el mismo componente en el mismo archivo. Son pequeños ahora, pero cada uno podría crecer mucho. Podríamos recibir nuevos requerimientos para uno y no para el otro. Cada cambio pone a ambos componentes en riesgo y dobla los esfuerzos necesarios de testing sin darnos beneficios. Si tuvieramos que utilizar los detalles del héroe en otro lado en nuestra app, nuestra lista de heroe se colaría con el.
De la forma en la que están hechas las cosas, estamos violando el Principio de Responsabilidad Única. Esto es solo un tutorial pero aún así podemos hacer las cosas bién, especialmente si hacerlas bien es facil y aprendemos a hacer aplicaciones de Angular en el proceso.
Vamos a separar el detalle del héroe en su propio componente.
Separando el componente del héroe
Agrega un nuevo archivo llamado hero-detail.component.ts al directorio app y crea el componente HeroDetailComponent de la siguiente manera:
src/app/hero-detail.component.ts (versión inicial)
import { Component, Input } from '@angular/core';
@Component({
selector: 'my-hero-detail',
})
export class HeroDetailComponent {
}Convenciones de nombrado
Nos gustaría identificar de un vistazo las clases que son componentes y cuales archivos contienen componentes.
Notemos que tenemos un AppComponent en un archivo llamado app.component.ts y nuestro nuevo HeroDetailComponent está en un archivo llamado hero-detail.component.ts.
Todos nuestros nombres de componente terminan en Component. Todos nuestros archivos de componente terminan en “.component”.
Escribimos los nombres de archivo con guiones (también llamado kebab-case) así no nos tenemos que preocupar por la sensibilidad a las mayúsculas en nuestro servidor o control de código fuente.
Comenzamos importando los decoradores Component e Input de Angular porque los vamos a necesitar pronto.
Creamos los metadatos con el decorador @Component donde especificamos el selector name (nombre) que identifica al elemento del componente en el html. Luego exportamos la clase para hacerla disponible a otros componentes.
Cuando terminemos con esto, vamos a importar nuestro nuevo componente en AppComponent y crear el correspondiente
elemento <my-hero-detail> en el template.
El template de detalles del héroe
En este momento, las vistas Heroes y Hero Detail están combinadas en un template en AppComponent. Cortemos el contenido de Hero Detail de AppComponent y peguémoslo en una nueva propiedad template de HeroDetailComponent.
Anteriormente enlazamos a la propiedad selectedHero.name del AppComponent. Nuestro HeroDetailComponent va a tener una propiedad hero, no una propiedad selectedHero. Entonces vamos a remplazar selectedHero con hero en todos los lugares de nuestro template.
Este será nuestro único cambio. El resultado se verá asi:
src/app/hero-detail.component.ts (template)
template: `
<div *ngIf="hero">
<h2> details!</h2>
<div><label>id: </label></div>
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name"/>
</div>
</div>
`Ahora nuestro detalle del heroe existe solamente en el HeroDetailComponent.
Agregar la propiedad Hero
Agreguemos la propiedad hero de la que estuvimos hablando a la clase del componente.
hero: hero;Uh oh. Declaramos la propiedad hero como tipo Hero pero nuestra clase Hero está en el archivo app.component.ts. Tenemos dos componentes, uno en cada archivo, que necesitan hacer referencia a la clase Hero.
Vamos a resolver este problema relocalizando nuestra clase Hero de app.compontent.ts a su propio archivo hero.ts.
src/app/hero.ts
export class Hero {
id: number;
name: string;
}Exportamos la clase Hero de hero.ts porque vamos a necesitar hacer una referencia a ella en ambos archivos de componentes. Agrega la siguiente sentencia import cerca de la parte de arriba de ambos app.component.ts y hero-detail.component.ts.
import { Hero } from './hero';La propiedad Hero es una entrada
El componente HeroDetailComponent necesita que le digan que héroe mostrar. ¿Quién le dirá? ¡Su componente padre AppComponent!
El componente AppComponent sabe que héroe mostrar: el héroe que el usuario ha seleccionado de la lista. La selección del usuario está guardada en la propiedad selectedHero.
Pronto vamos a actualizar el template del AppComponent para enlazar su propiedad selectedHero a la propiedad hero de nuestro HeroDetailComponent. El enlace debería quedar algo así:
<my-hero-detail [hero]="selectedHero"></my-hero-detail>Notemos que la propiedad hero es el destino del enlace de propiedad (está en corchetes a la izquierda del (=)).
Angular insiste en que declaremos la propiedad destino como una propiedad input. Si no lo hacemos, Angular rechaza el enlace y devuelve un error.
Hay un par de formas de declarar hero como input. Lo vamos a hacer de la forma que preferimos, anotando la propiedad hero con el decorador @Input que importamos previamente.
@Input()
hero: Hero;Actualizar el AppModule
Regresemos a AppModule, el módulo raiz de la aplicación, y enseñémosle como utilizar HeroDetailComponent.
Empezamos importando HeroDetailComponent asi podemos referirnos a él.
import { HeroDetailComponent } from './hero-detail.component';Luego agregamos HeroDetailComponent al decorador NgModule en la parte del arreglo declarations. Este arreglo contiene la lista de todos nuestros componentes, pipes, y directivas que creamos y pertenecen al módulo de nuestra aplicación.
@NgModule({
imports: [
BrowserModule,
FormsModule
],
declarations: [
AppComponent,
HeroDetailComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }Actualizamos el AppComponent
Ahora que nuestra aplicación conoce sobre HeroDetailComponent, buscamos la ubicación en el template del AppComponent donde quitamos el contenido del detalle del héroe y agregamos una etiqueta que representa al HeroDetailComponent.
<my-hero-detail></my-hero-detail>my-hero-detail es el nombre que elegimos como selector en los metadatos del HeroDetailComponent.
Ambos componentes no van a coordinar hasta que enlacemos la propiedad selectedHero del AppComponent a la propiedad hero del elemento HeroDetailComponent de esta forma:
<my-hero-detail [hero]="selectedHero"></my-hero-detail>El componente AppComponent debería verse de esta forma:
app.component.ts (template)
template: `
<h1></h1>
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge"></span>
</li>
</ul>
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
`,Gracias al enlace, el HeroDetailComponent debería recibir el héroe de AppComponent y mostrar el detalle del héroe debajo de la lista. El detalle debería actualizarse cada vez que el usuario elija un nuevo héroe.
Funciona
Cuando vemos nuestra app en el navegador, vemos una lista de héroes. Cuando seleccionamos un héroes podemos ver sus detalles.
Lo que es fundamentalmente nuevo es que podemos usar este HeroDetailComponent para mostrar detalles en cualquier parte de nuestra app.
¡Hemos creado nuestro primer componente reutilizable!
Revisando la estructura de la App
Verifiquemos que la estructura de la app que tengamos luego de esta buena refactorización sea la siguiente:
angular-tour-of-héroes
|-> src
|-> app
|-> app.component.ts
|-> app.module.ts
|-> hero.ts
|-> hero-detail.component.ts
|-> main.ts
|-> index.html
|-> styles.css
|-> systemjs.config.js
|-> tsconfig.json
|-> node_modules ...
|-> package.json
Acá están los archivos que hemos discutido en este capítulo:
src/app/hero-detail.component.ts
import { Component, Input } from '@angular/core';
import { Hero } from './hero';
@Component({
selector: 'my-hero-detail',
template: `
<div *ngIf="hero">
<h2> details!</h2>
<div><label>id: </label></div>
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name"/>
</div>
</div>
`
})
export class HeroDetailComponent {
@Input()
hero: Hero;
}src/app/app.component.ts
import { Component } from '@angular/core';
import { Hero } from './hero';
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' }
];
@Component({
selector: 'my-app',
template: `
<h1></h1>
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge"></span>
</li>
</ul>
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
`,
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;
}
`]
})
export class AppComponent {
title = 'Tour of Heroes';
heroes = HEROES;
selectedHero: Hero;
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
}src/app/hero.ts
export class Hero {
id: number;
name: string;
}src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { HeroDetailComponent } from './hero-detail.component';
@NgModule({
imports: [
BrowserModule,
FormsModule
],
declarations: [
AppComponent,
HeroDetailComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }El camino recorrido
Veamos que es lo que hemos construido:
- Creamos un componente reutilizable
- Aprendimos como hacer que un componente acepte una entrada
- Aprendimos a declarar directivas que necesitamos en nuestro módulo de Angular. Listamos las directivas que necesitamos en el decorador NgModule dentro del array Declarations
- Aprendimos a enlazar un componente padre con un componente hijo
Puedes ejecutar el ejemplo en vivo para esta parte.
El camino por delante
Nuestro Tour de los héroes se ha vuelto más reutilizable con componentes compartidos.
Aún estamos obteniendo nuestros datos (hardcodeados) desde el AppComponent. Eso no es sostenible. Deberíamso refactorizar el acceso a datos a un servicio separado y compartirlo con los componentes que necesiten los datos.
Vamos a aprender a crear servicios en el proximo capítulo del tutorial.