第二章 组件
点此处可以下载本章完成后的 示例代码 。运行方法为:
- 解压缩文件,进入
 heroes项目根目录。- 使用
 npm install补上依赖包。也可以把之前备份的node_modules目录复制一份在项目根目录下。- 使用
 npm start运行项目,打开浏览器访问http://localhost:4200/进入项目。
组件负责控制屏幕上的某一块区域,我们也称这块区域为视图,即组件是用来控制视图显示的逻辑。我们在类中定义组件的应用逻辑,为视图提供支持。组件通过一些由属性和方法组成的API与视图交互。
1. 编写第一个组件
下面我们开始新建第一个组件,这个组件的作用是在页面中显示一个Hero的信息。
命令行中进入该项目目录,使用命令 ng g c heroes/hero-list 新建组件文件。
这里的
ng g c heroes/hero-list是工具angular/cli的命令,使用了简写,不使用简写时的语句是这样ng generate component heroes/hero-list,参数generate是说明要生成文件,参数component是说明要生成一个组件,heroes/hero-list指的是要在src/app目录下建一个heroes/hero-list目录,我们要生成组件的名字就是hero-list。更多用法可以查看说明。
我们可以看到输出:
ng g c heroes/hero-list
installing component
  create src/app/heroes/hero-list/hero-list.component.css
  create src/app/heroes/hero-list/hero-list.component.html
  create src/app/heroes/hero-list/hero-list.component.spec.ts
  create src/app/heroes/hero-list/hero-list.component.ts
  update src/app/app.module.ts
说明angular/cli工具已经帮我们在app下新建一个heroes/hero-list文件夹,4个文件。注意ng命令同时更新了app.module.ts这个文件,如果想删除这个组件,除了删除对应的目录外,还应该删除app.module.ts中相关的内容。
|---hero-list.component.css           是CSS样式文件
|---hero-list.component.html          是html文件
|---hero-list.component.spec.ts       是测试文件,我们开发过程中几乎用不到
|---hero-list.component.ts            是我们需要的hero组件文件
打开hero-list.component.ts文件:
import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'app-hero-list',
  templateUrl: './hero-list.component.html',
  styleUrls: ['./hero-list.component.css']
})
export class HeroListComponent implements OnInit {
  constructor() { }
  ngOnInit() {
  }
}
这些都是
angular/cli工具自动填写的。
selector属性是CSS选择器的名字,app-hero-list会匹配元素的标签名,用于在父组>件中的模板中标记出当前组件的位置。(在父组件中使用标签<app-hero-list></app-hero-list>即可调用heroComponent组件。templateUrl属性是外联HTML文件stylesUrls属性是外联样式CSS文件。
组件的命名
- 组件的类名应该是
大驼峰形式,并且以 Component 结尾。例如一个显示英雄列表的组件名称为HeroListComponent。 - 组件的文件名应该是
小写中线形式,每个单词使用中线分隔,并且以.component.ts结尾。 
组件引入的包
组件的文件中需要引入名叫 Component 包。
例如,HeroListComponent组件中肯定需要包含这样一行代码:
import { Component } from '@angular/core';
请注意:要定义一个组件,总是要先导入符号 Component。
显示英雄名字
打开
hero-list.component.ts,添加变量hero。export class HeroListComponent implements OnInit { constructor() { } hero = 'The Monkey King'; ngOnInit() { } }打开
hero-list.component.html,修改内容如下。
文件:hero-list.component.html
<h1>Hello {{hero}}!</h1>
这里的双大括号是Angular中的插值表达式绑定语法。它们表示的组件中的
hero属性的值会作为字符串插入到HTML中间。这个文件中的代码称为模板,我们通过组件的自带的模板定义组件视图。模板以html形式存在,告诉Angular如何渲染组件。多数情况下,模板看起来很像标准的HTML,但是也有不同的地方。
- 打开
app.component.html。在文件内容替换为: 
<app-hero-list></app-hero-list>
打开浏览器,可以看到:
Hello The Monkey King!
目前为止,一个显示Hello的组件已经做好了。
下面我们来改进这个组件。
2. 英雄对象
显然,一个英雄有许多属性,所以我们需要把hero从一个字符串变量换成一个类。
创建一个Hero类,它具有id、name、desc属性。分别表示一个英雄的id,姓名,相关描述。
现在,把下列代码放到hero-list.component.ts的顶部,仅次于import语句。
export class Hero {
    id: number;
    name: string;
    desc: string;
}
一般我们总是
export组件类,因为我们肯定会在别的地方import它。
现在,有了一个Hero类,我们把组件中类型为string的hero属性换成类型为Hero的hero属性。然后以1为 id ,以The Monkey King为 name ,初始化它。
hero: Hero = {
    id: 1,
    name: 'The Monkey King',
    desc: 'I can fly!'
}
同时也要更新模板中的绑定表达式,来引用hero的两个属性。
文件:hero-list.component.html
<h1>Hello {{hero.name}}!</h1>
<h2>Your id is: {{hero.id}}</h2>
打开浏览器,我们可以看到修改后的结果了。
Hello The Monkey King!
Your id is 1
3. 编辑英雄
用户应该能在一个<input>输入框中编辑英雄的名字。当用户输入时,这个输入框应该能同时显示和修改英雄的<name>属性
也就是说,我们要在表单元素<input>和组件<hero.name>属性之间建立双向绑定。
双向绑定
打开hero-list.component.html文件,修改内容为:
<h1>Hello {{hero.name}}!</h1>
<h2>Your id is {{hero.id}}</h2>
<div>
    <label>name:</label>
    <input [(ngModel)]="hero.name" placeholder="name">
</div>
<div>
    <label>id:</label>
    <input [(ngModel)]="hero.id" placeholder="id">
</div>
[(ngModel)]是一个Angular语法,用与把hero.name绑定到输入框中,它的数据流是双向的:从属性到输入框,从输入框回到属性。 要使用[(ngModel)],我们必须导入FormsModule模块。
导入 FormsModule
打开app.module.ts文件,从@angular/forms导入符号FormsModule。然后把FormsModule添加到@NgModule元数据的imports数组中,它是当前应用正在使用的外部模块列表。
修改后的app.module.ts:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { HeroListComponent } from './heroes/hero-list/hero-list.component';
@NgModule({
  declarations: [
    AppComponent,
    HeroListComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
我们可以修改hero的名字和id,可以看到这个改动立刻体现在上方的内容中。
现在我们已经新建了一个组件,来显示一个英雄的信息。 实际情况是我们需要管理多个英雄,所以下面我们来扩展这个实例,让它显示英雄列表。
4. 英雄列表
调整组件结构
Angular官方指导建议一个ts文件中只有一个类,所以我们将之前的组件中的类放到新的文件中。
 将Hero类放到新文件中
- 使用命令
ng g class domain/hero新建Hero类的文件。 - 将
hero-list.component.ts中的Hero类的内容剪切到新建的文件中。 - 在
hero-list.component.ts中添加import { Hero } from '../../domain/hero'。 
在这里domain文件夹用来存放对象类文件
现在,hero-list.component.ts文件是:
import { Component, OnInit } from '@angular/core';
import { Hero } from '../../domain/hero';
@Component({
  selector: 'app-hero-list',
  templateUrl: './hero-list.component.html',
  styleUrls: ['./hero-list.component.css']
})
export class HeroListComponent implements OnInit {
  constructor() { }
  hero: Hero = {
    id: 1,
    name: 'The Monkey King',
    desc: 'I can fly!'
  };
  ngOnInit() {
  }
}
hero.ts文件是:
export class Hero {
    id: number;
    name: string;
    desc: string;
}
模拟数据
要想显示更多的hero信息,我们就需要获取一些hero数据,当然在实际情况中是从web服务器中获取数据,但现在我们用一个数组模拟web api。在后续的章节中,我们后介绍到web api的使用。
在app目录下新建一个文件mock-heroes.ts,在这个文件中我们添加一个hero数组:
import { Hero } from './domain/hero';
export const HEROES: Hero[] = [
    { id: 1, name: 'Mr.Nice' , desc: 'I am Mr.Nive. I like apple.'},
    { id: 2, name: 'Narco' , desc: 'I am Narco. I like banana.'},
    { id: 3, name: 'Bombasto' , desc: 'I am Bombasto. I like orange.'},
    { id: 4, name: 'Celeritas', desc: 'I am Celeritas. I like grape.'},
    { id: 5, name: 'Magneta', desc: 'I am Magneta. I like strawberry.'},
    { id: 6, name: 'Rubbermale', desc: 'I am Rubbermale. I like pears.'},
    { id: 7, name: 'Dynama', desc: 'I am Dynama. I like tomato.'},
    { id: 8, name: 'Dr IQ', desc: 'I am Dr IQ. I like peach.' },
    { id: 9, name: 'Magma', desc: 'I am Magma. I like sandwich.'},
    { id: 10, name: 'Tornado', desc: 'I am Tornado. I like hamburger.'}
];
别忘了
import Hero,因为hero类在另一个hero.ts中。
显示英雄列表
在hero-list.component.ts文件中,添加一个公共属性,用来暴露这些hero,以供绑定。
import { Component, OnInit } from '@angular/core';
import { Hero } from '../../domain/hero';
import { HEROES } from '../../mock-heroes';
@Component({
  selector: 'app-hero-list',
  templateUrl: './hero-list.component.html',
  styleUrls: ['./hero-list.component.css']
})
export class HeroListComponent implements OnInit {
  constructor() { }
  heroes = HEROES;
  ngOnInit() {
  }
}
在这里,我们没有明确定义
heroes属性的数据类型,是因为Typescript能从HEROES推断出来。
改变视图模板
我们在模板中用一个无序列表来显示英雄的名字。
打开hero-list.component.html文件,添加以下代码:
<h2>All heroes</h2>
<ul class="heroes">
  <li *ngFor="let hero of heroes">
    <span class="badge">{{hero.id}}</span> {{hero.name}}
  </li>
</ul>
这里,我们需要做的就是将模拟的数据填充到这个模板中去。
使用ngFor来显示英雄列表
我们想要把HEROES数组绑定到模板中,迭代并逐个显示它们。所以,我们使用了内置指令*ngFor。
ngFor的*前缀表示
及其子元素组成了一个主控模板。 ngFor指令在HeroComponent.heroes属性返回的heroes数组上迭代,并输出此模板的实例。 引号中赋值给ngFor的那段文本表示“从heroes数组中取出每个英雄,存入一个局部的hero变量,并让它在相应的模板实例中可用”。 要了解更多关于ngFor和模板输入变量的知识,查看用ngFor显示数组属性和模板语法章ngFor。 
这时候,我们打开浏览器可以看到,有了英雄的列表。

添加样式
英雄们是有了,但是,样子实在的太简陋了。我们需要为英雄们加上样式,这就要修改样式单文件。打开 hero.componet.css ,并添加如下的样式:
.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: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;
}
打开浏览器,我们可以看到英雄列表的样式发生了变化。

小结
到此为止,你已经可以显示或修改一个英雄的信息,也可以显示一个英雄列表,但是如果想查看英雄的详细的信息或者修改列表中的英雄信息,我们需要更多的组件,这样代码会更清晰。