El editor de Héroes
La aplicación ya tiene su título. Ahora crearemos un nuevo componente para mostrar la información del héroe y colocaremos dicho componente en el caparazón de la aplicación.
Crea el componente heroes
Usando el CLI de Angular, generamos un nuevo componente llamado heroes
.
1 2 3 |
ng generate component heroes |
CLI crea una nueva carpeta, src/app/heroes/
y genera los tres ficheros del HeroesComponent
.
El fichero de la clase HeroesComponent
es:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-heroes', templateUrl: './heroes.component.html', styleUrls: ['./heroes.component.css'] }) export class HeroesComponent implements OnInit { constructor() { } ngOnInit() { } } |
Hay que importar siempre el símbolo Component
de la librería principal (core) y anotar la clase del componente con @Component
.
@Component
es una función decorador que especifica los metadatos de Angular para el componente.
CLI ha generado tres propiedades de metadatos:
selector
– el elemento selector del componente.templateUrl
– la ubicación del fichero plantilla del componente.styleUrls
– la ubicación de los estilos CSS privados del componente.
El elemento selector, 'app-heroes'
, es el nombre del elemento HTML que identifica este componente dentro de la plantilla del componente padre.
El ngOnInit
es un ‘enganche del ciclo de vida’ (lifecycle hook). Angular llama a ngOnInit
justo después de crear un componente. Es un buen lugar para colocar la lógica de inicialización.
Exporta (export
) siempre la clase del componente de modo que se pueda importar (import
) desde otro punto… como sucede en AppModule
Añade la propiedad hero
Añade la propiedad hero
a HeroesComponent
para un héroe llamado «Windstorm».
1 |
hero = 'Windstorm'; |
Muestra el héroe
Abre el fichero plantilla heroes.component.html
. Borra el texto por defecto generado por el CLI de Angular y reemplázalo con una vinculación a la nueva propiedad hero
.
1 |
{{hero}} |
Mostrar la vista HeroesComponent
Para mostrar HeroesComponent
, debes añadirlo a la plantilla del caparazón AppComponent
Recuerda que app-heroes
es el elemento selector para HeroesComponent
. Así que añade un elemento app-heroes
al fichero plantilla de AppComponent
, justo debajo del título.
1 2 |
<h1>{{title}}</h1> <app-heroes></app-heroes> |
Asumiendo que el comando de CLI ng serve
aún está ejecutándose, el navegador debería actualizarse y mostrar tanto el título de la aplicación como el nombre del héroe.
Crea una clase Hero
Un héroe auténtico no es tan sólo un nombre.
Crea una clase Hero
en su propio fichero dentro de la carpeta src/app
. Dale las propiedades id
y name
.
1 2 3 4 |
export class Hero { id: number; name: string; } |
Vuelve a la clase HeroesComponente
e importa la clase Hero
.
Refactoriza la propiedad hero
del componente para que sea del tipo Hero
. Inicialízala con un id
de 1
y el nombre Windstorm
.
La clases HeroesComponent
revisada debería quedar así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import { Component, OnInit } from '@angular/core'; import { Hero } from '../hero'; @Component({ selector: 'app-heroes', templateUrl: './heroes.component.html', styleUrls: ['./heroes.component.css'] }) export class HeroesComponent implements OnInit { hero: Hero = { id: 1, name: 'Windstorm' }; constructor() { } ngOnInit() { } } |
La página ya no se muestra correctamente porque hemos cambiado el héroe de cadena (string) a objeto.
Muestra el objeto Hero
Actualiza el vínculo en la plantilla para mostrar el nombre del héroe y su id en una vista de detalle como esta:
1 2 3 |
<h2>{{ hero.name }} Details</h2> <div><span>id: </span>{{hero.id}}</div> <div><span>name: </span>{{hero.name}}</div> |
El navegador se actualizará y mostrará la información del héroe.
Formatea con UppercasePipe
Modifica el vínculo hero.name
de esta manera.
1 |
<h2>{{ hero.name | uppercase }} Details</h2> |
El navegador se actualiza y ahora el nombre del héroe se muestra en letra mayúscula.
La palabra uppercase
en el vínculo de interpolación, justo después del operador ‘tubería’ (pipe |), activa la acción UppercasePipe
.
Las tuberías (pipes) son una buena forma de formatear cadenas, importes, fechas y otros datos a mostrar. Angular contiene varías tuberías incorporadas y es posible crear nuevas.
Edita el héroe
Los usuarios deberían ser capaces de editar el nombre del héroe en una caja de texto <input>
.
La caja de texto debería tanto mostrar la propiedad name
del héroe como actualizar dicha propiedad a medida que el usuario escribe. Eso significa que los datos viajan desde la clase del componente hacia la pantalla y desde la pantalla de vuelta a la clase.
Para automatizar este flujo de datos, configuramos una vínculo de datos en dos sentidos entre el elemento del formulario input
y la propiedad hero.name
.
Vínculo de doble sentido (Two-way binding)
Refactoriza la parte del detalle en la plantillaHeroesComponent
para que quede así:
1 2 3 4 5 |
<div> <label>name: <input [(ngModel)]="hero.name" placeholder="name"> </label> </div> |
[(ngModel)] es la sintaxis Angular para la vinculacíon de doble sentido.
Aquí vincula la propiedad hero.name
a la caja de texto HTML, de modo que los datos pueden fluir en ambas direcciones: desde la propiedad hero.name
a la caja de texto y desde la caja de texto de vuelta a hero.name
.
Falta FormsModule
Observa que la aplicación ha dejado de funcionar cuando hemos añadido [(ngModel)]
.
Para ver el error, abre las herramientas de desarrollo del navegador y busca en la consola un mensaje como
Template parse errors:
Can’t bind to ‘ngModel’ since it isn’t a known property of ‘input’.
Aunque ngModel
es una directiva válida de Angular, no está disponible por defecto.
Pertenece a FormsModule
y debes importarlo para poder usarlo.
AppModule
Angular necesita saber que trozos de tu aplicación encajan y que ficheros y librerías requiere. A esta información se le llama metadatos. Algunos de los metadatos están en los decoradores @Component
que añadiste a las clases de tus componentes. Otros metadatos críticos están en los decoradores @NgModule.
Importa FormsModule
Abre AppModule
(app.module.ts
) e importa el símbolo FormsModule
desde la librería @angular/forms
1 |
import { FormsModule } from '@angular/forms'; // <-- NgModel está aquí |
Después, añade FormsModule
al array de imports
de los metadatos de @NgModule
, que contiene una lista de módulos externos que la aplicación necesita.
1 2 3 4 |
imports: [ BrowserModule, FormsModule ], |
Cuando el navegador se actualiza, la aplicación debería funcionar de nuevo. Puedes editar el nombre del héroe y ver como los cambios se reflejan inmediatamente en el elemento <h2>
sobre la caja de texto.
Declara HeroesComponent
Cada componente debe declararse en sólo un NgModule.
No hemos declarado el componente HeroesComponent
. Entonces, ¿por qué ha funcionado la aplicación?
Porque el CLI de Angular ha declarado HeroesComponent
en AppModule
cuando generó el componente.
Abre src/app/app.module.ts
y encuentra HeroesComponent
importado en la parte de arriba.
1 |
import { HeroesComponent } from './heroes/heroes.component'; |
El componente HeroesComponent
está declarado en el array @NgModule.declarations
1 2 3 4 |
declarations: [ AppComponent, HeroesComponent ], |
Fíjate que AppModule
declara los dos componentes de la aplicación, AppComponent
y HeroesComponent
.
Revisión final del código
Tu aplicación debería parecerse a este ejemplo / descarga ejemplo. Aquí tienes los ficheros tratados en esta página
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import { Component, OnInit } from '@angular/core'; import { Hero } from '../hero'; @Component({ selector: 'app-heroes', templateUrl: './heroes.component.html', styleUrls: ['./heroes.component.css'] }) export class HeroesComponent implements OnInit { hero: Hero = { id: 1, name: 'Windstorm' }; constructor() { } ngOnInit() { } } |
1 2 3 4 5 6 7 |
<h2>{{ hero.name | uppercase }} Details</h2> <div><span>id: </span>{{hero.id}}</div> <div> <label>name: <input [(ngModel)]="hero.name" placeholder="name"> </label> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; // <-- NgModel lives here import { AppComponent } from './app.component'; import { HeroesComponent } from './heroes/heroes.component'; @NgModule({ declarations: [ AppComponent, HeroesComponent ], imports: [ BrowserModule, FormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } |
1 2 3 4 5 6 7 8 9 10 |
import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'Tour of Heroes'; } |
1 2 |
<h1>{{title}}</h1> <app-heroes></app-heroes> |
1 2 3 4 |
export class Hero { id: number; name: string; } |
Resumen
- Hemos usado el CLI para generar un segundo componente
HeroesComponent
. - Hemos mostrado
HeroesComponent
añadiéndolo al caparazónAppComponent
. - Hemos aplicado
UppercasePipe
para formatear el nombre. - Hemos usado la vinculación bidireccional con la directiva
ngModel
. - Hemos aprendido acerca de
AppModule
. - Hemos importado
FormsModule
enAppModule
de modo que Angular reconozca y aplique la directivangModule
. - Hemos aprendido la importancia de declarar los componentes en
AppModule
y ver como CLI los declara por nosotros.
Nota: puedes encontrar el documento original de esta entrada en https://angular.io/tutorial/toh-pt1