本页中,你将扩展《英雄之旅》应用,让它显示一个英雄列表,并允许用户选择一个英雄,查看该英雄的详细信息。
你需要一些英雄数据以供显示。
最终,你会从远端的数据服务器获取它。不过目前,你要先创建一些模拟的英雄数据,并假装它们是从服务器上取到的。
在 src/app/
文件夹中创建一个名叫 mock-heroes.ts
的文件。定义一个包含十个英雄的常量数组 HEROES
,并导出它。该文件是这样的。
import { Hero } from './hero';
export const HEROES: Hero[] = [
{ id: 12, name: 'Dr. Nice' },
{ 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' }
];
打开 HeroesComponent
类文件,并导入模拟的 HEROES
。
import { HEROES } from '../mock-heroes';
往类中添加一个 heroes
属性,这样可以暴露出这个 HEROES
数组,以供绑定。
export class HeroesComponent implements OnInit {
heroes = HEROES;
}
打开 HeroesComponent
的模板文件,并做如下修改:
<h2>
。<ul>
) 元素。<ul>
中插入 <li>
。<li>
中放一个 <button>
元素,以便在 <span>
元素中显示单个 hero
的属性。做完之后应该是这样的:
<h2>My Heroes</h2>
<ul class="heroes">
<li>
<button type="button">
<span class="badge">{{hero.id}}</span>
<span class="name">{{hero.name}}</span>
</button>
</li>
</ul>
由于属性 'hero' 不存在,因此会显示一个错误。要访问每个英雄并列出所有英雄,请在 <li>
上添加 *ngFor
以遍历英雄列表:
<li *ngFor="let hero of heroes">
*ngFor
是一个 Angular 的复写器(repeater)指令。它会为列表中的每项数据复写它的宿主元素。
这个例子中涉及的语法如下:
语法 | 详情 |
---|---|
| 宿主元素。 |
| 来自 |
| 保存列表每次迭代的当前 hero 对象。 |
不要忘了 ngFor
前面的星号(*
),它是该语法中的关键部分。
浏览器刷新之后,英雄列表出现了。
交互元素
注意:
在 <li>
元素中,我们将英雄的详细信息包装在 <button>
元素中。稍后我们使 hero 可点击,并且出于无障碍性的目的,最好使用本机交互式 HTML 元素(例如 <button>
),而不是向非交互式元素添加事件侦听器(例如 <li>
)。
英雄列表应该富有吸引力,并且当用户把鼠标移到某个英雄上和从列表中选中某个英雄时,应该给出视觉反馈。
在教程的第一章,你曾在 styles.css
中为整个应用设置了一些基础的样式。但那个样式表并不包含英雄列表所需的样式。
固然,你可以把更多样式加入到 styles.css
,并且放任它随着你添加更多组件而不断膨胀。
但还有更好的方式。你可以定义属于特定组件的私有样式,并且让组件所需的一切(代码、HTML 和 CSS)都放在一起。
这种方式让你在其它地方复用该组件更加容易,并且即使全局样式和这里不一样,组件也仍然具有期望的外观。
你可以用多种方式定义私有样式,或者内联在 @Component.styles
数组中,或者在 @Component.styleUrls
所指出的样式表文件中。
当 CLI 生成 HeroesComponent
时,它也同时为 HeroesComponent
创建了空白的 heroes.component.css
样式表文件,并且让 @Component.styleUrls
指向它,就像这样。
@Component({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css']
})
打开 heroes.component.css
文件,并且把 HeroesComponent
的私有 CSS 样式粘贴进去。
@Component
元数据中指定的样式和样式表都是局限于该组件的。heroes.component.css
中的样式只会作用于 HeroesComponent
,既不会影响到组件外的 HTML,也不会影响到其它组件中的 HTML。
当用户在此列表中点击一个英雄时,该组件应该在页面底部显示所选英雄的详情。
在本节,你将监听英雄条目的点击事件,并显示与更新英雄的详情。
为 <li>
中的 <button>
上添加一个点击事件的绑定代码:
<li *ngFor="let hero of heroes">
<button type="button" (click)="onSelect(hero)">
<!-- ... -->
click
外面的圆括号会让 Angular 监听这个 <button>
元素的 click
事件。 当用户点击 <button>
时,Angular 就会执行表达式 onSelect(hero)
。
下一部分,会在 HeroesComponent
上定义一个 onSelect()
方法,用来显示 *ngFor
表达式所定义的那个英雄(hero
)。
把该组件的 hero
属性改名为 selectedHero
,但不要为它赋值。 因为应用刚刚启动时并没有所选英雄。
添加如下 onSelect()
方法,它会把模板中被点击的英雄赋值给组件的 selectedHero
属性。
selectedHero?: Hero;
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
现在,组件的模板中有一个列表。要想点击列表中的一个英雄,并显示该英雄的详情,你需要在模板中留一个区域,用来显示这些详情。在 heroes.component.html
中该列表的紧下方,添加如下代码:
<div *ngIf="selectedHero">
<h2>{{selectedHero.name | uppercase}} Details</h2>
<div>id: {{selectedHero.id}}</div>
<div>
<label for="hero-name">Hero name: </label>
<input id="hero-name" [(ngModel)]="selectedHero.name" placeholder="name">
</div>
</div>
只有在选择英雄时才会显示英雄详细信息。最初创建组件时,没有所选的 hero,因此我们将 *ngIf
指令添加到包装 hero 详细信息的 <div>
中,以指示 Angular 仅在实际定义 selectedHero
时(在它被通过点击英雄来选择)。
不要忘了 ngIf
前面的星号(*
),它是该语法中的关键部分。
为了标出选定的英雄,你可以在以前添加过的样式中增加 CSS 类 .selected
。若要把 .selected
类应用于此 <li> 上,请使用类绑定。
Angular 的类绑定可以有条件地添加和删除 CSS 类。只需将 [class.some-css-class]="some-condition"
添加到要设置样式的元素即可。
在 HeroesComponent
模板中的 <button>
元素上添加 [class.selected]
绑定,代码如下:
[class.selected]="hero === selectedHero"
如果当前行的英雄和 selectedHero
相同,Angular 就会添加 CSS 类 selected
,否则就会移除它。
最终的 <li>
是这样的:
<li *ngFor="let hero of heroes">
<button [class.selected]="hero === selectedHero" type="button" (click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span>
<span class="name">{{hero.name}}</span>
</button>
</li>
下面是本页面中所提及的代码文件,包括 HeroesComponent
的样式。
import { Hero } from './hero';src/app/heroes/heroes.component.ts
export const HEROES: Hero[] = [
{ id: 12, name: 'Dr. Nice' },
{ 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' }
];
import { Component, OnInit } from '@angular/core';src/app/heroes/heroes.component.html
import { Hero } from '../hero';
import { HEROES } from '../mock-heroes';
@Component({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
heroes = HEROES;
selectedHero?: Hero;
constructor() { }
ngOnInit(): void {
}
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
}
<h2>My Heroes</h2>src/app/heroes/heroes.component.css
<ul class="heroes">
<li *ngFor="let hero of heroes">
<button [class.selected]="hero === selectedHero" type="button" (click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span>
<span class="name">{{hero.name}}</span>
</button>
</li>
</ul>
<div *ngIf="selectedHero">
<h2>{{selectedHero.name | uppercase}} Details</h2>
<div>id: {{selectedHero.id}}</div>
<div>
<label for="hero-name">Hero name: </label>
<input id="hero-name" [(ngModel)]="selectedHero.name" placeholder="name">
</div>
</div>
/* HeroesComponent's private CSS styles */
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
display: flex;
}
.heroes button {
flex: 1;
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: 0;
border-radius: 4px;
display: flex;
align-items: stretch;
height: 1.8em;
}
.heroes button:hover {
color: #2c3a41;
background-color: #e6e6e6;
left: .1em;
}
.heroes button:active {
background-color: #525252;
color: #fafafa;
}
.heroes button.selected {
background-color: black;
color: white;
}
.heroes button.selected:hover {
background-color: #505050;
color: white;
}
.heroes button.selected:active {
background-color: black;
color: white;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #405061;
line-height: 1em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
.heroes .name {
align-self: center;
}
*ngFor
显示了一个列表。 *ngIf
来根据条件包含或排除了一段 HTML。 class
绑定来切换 CSS 的样式类。