Angular教程-英雄之旅
本教程需要完成的工作:
- 使用 Angular 的内置指令来显示 / 隐藏元素,并显示英雄数据的列表。
- 创建 Angular 组件以显示英雄的详情,并显示一个英雄数组。
- 为只读数据使用单向数据绑定。
- 添加可编辑字段,使用双向数据绑定来更新模型。
- 把组件中的方法绑定到用户事件上,比如按键和点击。
- 让用户可以在主列表中选择一个英雄,然后在详情视图中编辑他。
- 使用管道来格式化数据。
- 创建共享的服务来管理这些英雄。
- 使用路由在不同的视图及其组件之间导航。
编辑英雄名字
用户应该能在一个 <input>
输入框中编辑英雄的名字。
当用户输入时,这个输入框应该能同时显示和修改英雄的 name
属性。 也就是说,数据流从组件类流出到屏幕,并且从屏幕流回到组件类。
要想让这种数据流动自动化,就要在表单元素 <input>
和组件的 hero.name
属性之间建立双向数据绑定。
双向绑定
把模板中的英雄详情区重构成这样:
<div>
<label>name:
<input [(ngModel)]="hero.name" placeholder="name"/>
</label>
</div>
ngModel
是一个有效的 Angular 指令,不过在默认情况下是不可用的。
它属于一个可选模块 FormsModule
,必须自行添加此模块才能使用该指令
import {
FormsModule } from '@angular/forms';
//在NgModule中
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
],
注意:每个组件都必须声明在(且只能声明在)一个NgModule中。
显示英雄列表
创建模拟(mock)的英雄数据
import {
Hero } from './hero';
export const HEROES: Hero[] = [
{
id: 11, name: 'Dr 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
元数据中指定的样式和样式表都是局限于该组件的。 heroes.component.css
中的样式只会作用于 HeroesComponent
,既不会影响到组件外的 HTML,也不会影响到其它组件中的 HTML。
*ngFor
是一个 Angular 的复写器(repeater)指令。 它会为列表中的每项数据复写它的宿主元素。
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes" [class.selected]="hero === selectedHero" (click)="onSelect(hero)">
<!-- click 外面的圆括号会让 Angular 监听这个 <li> 元素的 click 事件。 当用户点击 <li> 时,Angular 就会执行表达式 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>
下面的写法是错误的:
原因:当应用启动时,selectedHero
是 undefined
,但模板中的绑定表达式引用了 selectedHero
的属性(表达式为 {
{selectedHero.name}}
),这必然会失败,因为还没选过英雄。
正确的写法是使用*ngFor隐藏,该组件只有当元素存在时才会显示
原理:当 selectedHero
为 undefined
时,ngIf
从 DOM 中移除了英雄详情。因此也就不用关心 selectedHero
的绑定了。当用户选择一个英雄时,selectedHero
也就有了值,并且 ngIf
把英雄的详情放回到 DOM 中。
<h2>{
{selectedHero.name | uppercase}} Details</h2>
<div><span>id: </span>{
{selectedHero.id}}</div>
<div>
<label>name:
<input [(ngModel)]="selectedHero.name" placeholder="name"/>
</label>
</div>
Angular 的 CSS类绑定机制机制让根据条件添加或移除一个 CSS 类变得很容易。 只要把 [class.some-css-class]="some-condition"
添加到你要施加样式的元素上就可以了。
把英雄详情移入一个独立的、可复用的组件。
<!--hero-detail.component.html-->
<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>
HeroDetailComponent
模板中绑定了组件中的 hero
属性,它的类型是 Hero
。将Hero符号导入。
hero
属性必须是一个带有@Input()
装饰器的输入属性,因为外部的 HeroesComponent
组件将会绑定到它。
<!--hero.component.html-->
<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>
<!--hero组件的html,在其中调用了绑定了detail组件-->
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
<!--[hero]="selectedHero" 是 Angular 的属性绑定语法。
这是一种单向数据绑定。从 HeroesComponent 的 selectedHero 属性绑定到目标元素的 hero 属性,并映射到了 HeroDetailComponent 的 hero 属性。-->
服务
为什么需要服务
组件不应该直接获取或保存数据,它们不应该了解是否在展示假数据。 它们应该聚焦于展示数据,而把数据访问的职责委托给某个服务。
@Injectable()
服务
注意,这个新的服务导入了 Angular 的 Injectable
符号,并且给这个服务类添加了 @Injectable()
装饰器。 它把这个类标记为依赖注入系统的参与者之一。HeroService
类将会提供一个可注入的服务,并且它还可以拥有自己的待注入的依赖。 目前它还没有依赖,但是[很快就会有了。
@Injectable()
装饰器会接受该服务的元数据对象,就像 @Component()
对组件类的作用一样。
获取英雄数据
HeroService
可以从任何地方获取数据:Web 服务、本地存储(LocalStorage)或一个模拟的数据源。
提供(provide) HeroService
必须先注册一个服务提供者,来让 HeroService
在依赖注入系统中可用,Angular 才能把它注入到 HeroesComponent
中。所谓服务提供者就是某种可用来创建或交付一个服务的东西;在这里,它通过实例化 HeroService
类,来提供该服务。
为了确保 HeroService
可以提供该服务,就要使用注入器来注册它。注入器是一个对象,负责当应用要求获取它的实例时选择和注入该提供者。
默认情况下,Angular CLI 命令 ng generate service
会通过给 @Injectable()
装饰器添加 providedIn: 'root'
元数据的形式,用根注入器将你的服务注册成为提供者。
<!--hero.service.ts-->
import {
Injectable } from '@angular/core';
import {
Hero} from './hero';
import {
HEROES} from './mock-heroes';
@Injectable({
providedIn: 'root'
})
export class HeroService {
constructor() {
}
getHeroes(): Hero[] {
return HEROES;
}
}