第二章 组件
点此处可以下载本章完成后的 示例代码 。运行方法为:
- 解压缩文件,进入
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;
}
打开浏览器,我们可以看到英雄列表的样式发生了变化。
小结
到此为止,你已经可以显示或修改一个英雄的信息,也可以显示一个英雄列表,但是如果想查看英雄的详细的信息或者修改列表中的英雄信息,我们需要更多的组件,这样代码会更清晰。