填写这份《一分钟调查》,帮我们(开发组)做得更好!去填写Home

显示英雄列表

Display a selection list

本页中,你将扩展《英雄之旅》应用,让它显示一个英雄列表, 并允许用户选择一个英雄,查看该英雄的详细信息。

In this page, you'll expand the Tour of Heroes application to display a list of heroes, and allow users to select a hero and display the hero's details.

要查看本页所讲的范例程序,参阅现场演练 / 下载范例

For the sample application that this page describes, see the现场演练 / 下载范例.

创建模拟(mock)的英雄数据

Create mock heroes

你需要一些英雄数据以供显示。

You'll need some heroes to display.

最终,你会从远端的数据服务器获取它。 不过目前,你要先创建一些模拟的英雄数据,并假装它们是从服务器上取到的。

Eventually you'll get them from a remote data server. For now, you'll create some mock heroes and pretend they came from the server.

src/app/ 文件夹中创建一个名叫 mock-heroes.ts 的文件。 定义一个包含十个英雄的常量数组 HEROES,并导出它。 该文件是这样的。

Create a file called mock-heroes.ts in the src/app/ folder. Define a HEROES constant as an array of ten heroes and export it. The file should look like this.

src/app/mock-heroes.ts
      
      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' }
];
    

显示这些英雄

Displaying heroes

打开 HeroesComponent 类文件,并导入模拟的 HEROES

Open the HeroesComponent class file and import the mock HEROES.

src/app/heroes/heroes.component.ts (import HEROES)
      
      import { HEROES } from '../mock-heroes';
    

往类中添加一个 heroes 属性,这样可以暴露出这个 HEROES 数组,以供绑定。

In the same file (HeroesComponent class), define a component property called heroes to expose the HEROES array for binding.

src/app/heroes/heroes.component.ts
      
      export class HeroesComponent implements OnInit {

  heroes = HEROES;
}
    

使用 *ngFor 列出这些英雄

List heroes with *ngFor

打开 HeroesComponent 的模板文件,并做如下修改:

Open the HeroesComponent template file and make the following changes:

  • 在顶部添加 <h2>

    Add an <h2> at the top,

  • 然后添加表示无序列表的 HTML 元素(<ul>

    Below it add an HTML unordered list (<ul>)

  • <ul> 中插入一个 <li> 元素,以显示单个 hero 的属性。

    Insert an <li> within the <ul> that displays properties of a hero.

  • 点缀上一些 CSS 类(稍后你还会添加更多 CSS 样式)。

    Sprinkle some CSS classes for styling (you'll add the CSS styles shortly).

做完之后应该是这样的:

Make it look like this:

heroes.component.html (heroes template)
      
      <h2>My Heroes</h2>
<ul class="heroes">
  <li>
    <span class="badge">{{hero.id}}</span> {{hero.name}}
  </li>
</ul>
    

由于属性 'hero' 不存在,因此会显示一个错误。要访问每个英雄并列出所有英雄,请在 <li> 上添加 *ngFor 以遍历英雄列表:

That displays an error since the property 'hero' does not exist. To have access to each individual hero and list them all, add an *ngFor to the <li> to iterate through the list of heroes:

      
      <li *ngFor="let hero of heroes">
    

*ngFor是一个 Angular 的复写器(repeater)指令。 它会为列表中的每项数据复写它的宿主元素。

The *ngForis Angular's repeater directive. It repeats the host element for each element in a list.

这个例子中涉及的语法如下:

The syntax in this example is as follows:

  • <li> 就是 *ngFor 的宿主元素。

    <li> is the host element.

  • heroes 就是来自 HeroesComponent 类的列表。

    heroes holds the mock heroes list from the HeroesComponent class, the mock heroes list.

  • 当依次遍历这个列表时,hero 会为每个迭代保存当前的英雄对象。

    hero holds the current hero object for each iteration through the list.

不要忘了 ngFor 前面的星号(*),它是该语法中的关键部分。

Don't forget the asterisk (*) in front of ngFor. It's a critical part of the syntax.

浏览器刷新之后,英雄列表出现了。

After the browser refreshes, the list of heroes appears.

给英雄列表“美容”

Style the heroes

英雄列表应该富有吸引力,并且当用户把鼠标移到某个英雄上和从列表中选中某个英雄时,应该给出视觉反馈。

The heroes list should be attractive and should respond visually when users hover over and select a hero from the list.

教程的第一章,你曾在 styles.css 中为整个应用设置了一些基础的样式。 但那个样式表并不包含英雄列表所需的样式。

In the first tutorial, you set the basic styles for the entire application in styles.css. That stylesheet didn't include styles for this list of heroes.

固然,你可以把更多样式加入到 styles.css,并且放任它随着你添加更多组件而不断膨胀。

You could add more styles to styles.css and keep growing that stylesheet as you add components.

但还有更好的方式。你可以定义属于特定组件的私有样式,并且让组件所需的一切(代码、HTML 和 CSS)都放在一起。

You may prefer instead to define private styles for a specific component and keep everything a component needs— the code, the HTML, and the CSS —together in one place.

这种方式让你在其它地方复用该组件更加容易,并且即使全局样式和这里不一样,组件也仍然具有期望的外观。

This approach makes it easier to re-use the component somewhere else and deliver the component's intended appearance even if the global styles are different.

你可以用多种方式定义私有样式,或者内联在 @Component.styles 数组中,或者在 @Component.styleUrls 所指出的样式表文件中。

You define private styles either inline in the @Component.styles array or as stylesheet file(s) identified in the @Component.styleUrls array.

当 CLI 生成 HeroesComponent 时,它也同时为 HeroesComponent 创建了空白的 heroes.component.css 样式表文件,并且让 @Component.styleUrls 指向它,就像这样:

When the CLI generated the HeroesComponent, it created an empty heroes.component.css stylesheet for the HeroesComponent and pointed to it in @Component.styleUrls like this.

src/app/heroes/heroes.component.ts (@Component)
      
      @Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})
    

打开 heroes.component.css 文件,并且把 HeroesComponent 的私有 CSS 样式粘贴进去。 你可以在本指南底部的查看最终代码中找到它们。

Open the heroes.component.css file and paste in the private CSS styles for the HeroesComponent. You'll find them in the final code review at the bottom of this guide.

@Component 元数据中指定的样式和样式表都是局限于该组件的。 heroes.component.css 中的样式只会作用于 HeroesComponent,既不会影响到组件外的 HTML,也不会影响到其它组件中的 HTML。

Styles and stylesheets identified in @Component metadata are scoped to that specific component. The heroes.component.css styles apply only to the HeroesComponent and don't affect the outer HTML or the HTML in any other component.

查看详情

Viewing details

当用户在此列表中点击一个英雄时,该组件应该在页面底部显示所选英雄的详情。

When the user clicks a hero in the list, the component should display the selected hero's details at the bottom of the page.

在本节,你将监听英雄条目的点击事件,并更新英雄的详情。

In this section, you'll listen for the hero item click event and update the hero detail.

添加 click 事件绑定

Add a click event binding

再往 <li> 元素上插入一句点击事件的绑定代码:

Add a click event binding to the <li> like this:

heroes.component.html (template excerpt)
      
      <li *ngFor="let hero of heroes" (click)="onSelect(hero)">
    

这是 Angular 事件绑定 语法的例子。

This is an example of Angular's event binding syntax.

click 外面的圆括号会让 Angular 监听这个 <li> 元素的 click 事件。 当用户点击 <li> 时,Angular 就会执行表达式 onSelect(hero)

The parentheses around click tell Angular to listen for the <li> element's click event. When the user clicks in the <li>, Angular executes the onSelect(hero) expression.

下一部分,会在 HeroesComponent 上定义一个 onSelect() 方法,用来显示 *ngFor 表达式所定义的那个英雄(hero)。

In the next section, define an onSelect() method in HeroesComponent to display the hero that was defined in the *ngFor expression.

添加 click 事件处理器

Add the click event handler

把该组件的 hero 属性改名为 selectedHero,但不要为它赋值。 因为应用刚刚启动时并没有所选英雄

Rename the component's hero property to selectedHero but don't assign it. There is no selected hero when the application starts.

添加如下 onSelect() 方法,它会把模板中被点击的英雄赋值给组件的 selectedHero 属性。

Add the following onSelect() method, which assigns the clicked hero from the template to the component's selectedHero.

src/app/heroes/heroes.component.ts (onSelect)
      
      selectedHero?: Hero;
onSelect(hero: Hero): void {
  this.selectedHero = hero;
}
    

添加详情区

Add a details section

现在,组件的模板中有一个列表。要想点击列表中的一个英雄,并显示该英雄的详情,你需要在模板中留一个区域,用来显示这些详情。 在 heroes.component.html 中该列表的紧下方,添加如下代码:

Currently, you have a list in the component template. To click on a hero on the list and reveal details about that hero, you need a section for the details to render in the template. Add the following to heroes.component.html beneath the list section:

heroes.component.html (selected hero details)
      
      <h2>{{selectedHero.name | uppercase}} Details</h2>
<div><span>id: </span>{{selectedHero.id}}</div>
<div>
  <label for="hero-name">Hero name: </label>
  <input id="hero-name" [(ngModel)]="selectedHero.name" placeholder="name">
</div>
    

刷新浏览器,应用挂了。

After the browser refreshes, the application is broken.

打开浏览器的开发者工具,它的控制台中显示出如下错误信息:

Open the browser developer tools and look in the console for an error message like this:

      
      HeroesComponent.html:3 ERROR TypeError: Cannot read property 'name' of undefined
    

怎么回事?

What happened?

当应用启动时,selectedHeroundefined设计如此

When the application starts, the selectedHero is undefined by design.

但模板中的绑定表达式引用了 selectedHero 的属性(表达式为 {{selectedHero.name}}),这必然会失败,因为你还没选过英雄呢。

Binding expressions in the template that refer to properties of selectedHero—expressions like {{selectedHero.name}}must fail because there is no selected hero.

修复 —— 使用 *ngIf 隐藏空白的详情

The fix - hide empty details with *ngIf

该组件应该只有当 selectedHero 存在时才显示所选英雄的详情。

The component should only display the selected hero details if the selectedHero exists.

把显示英雄详情的 HTML 包裹在一个 <div> 中。 并且为这个 div 添加 Angular 的 *ngIf 指令,把它的值设置为 selectedHero

Wrap the hero detail HTML in a <div>. Add Angular's *ngIf directive to the <div> and set it to selectedHero.

不要忘了 ngIf 前面的星号(*),它是该语法中的关键部分。

Don't forget the asterisk (*) in front of ngIf. It's a critical part of the syntax.

src/app/heroes/heroes.component.html (*ngIf)
      
      <div *ngIf="selectedHero">

  <h2>{{selectedHero.name | uppercase}} Details</h2>
  <div><span>id: </span>{{selectedHero.id}}</div>
  <div>
    <label for="hero-name">Hero name: </label>
    <input id="hero-name" [(ngModel)]="selectedHero.name" placeholder="name">
  </div>

</div>
    

浏览器刷新之后,英雄名字的列表又出现了。 详情部分仍然是空。 从英雄列表中点击一个英雄,它的详情就出现了。 应用又能工作了。 英雄们出现在列表中,而被点击的英雄出现在了页面底部。

After the browser refreshes, the list of names reappears. The details area is blank. Click a hero in the list of heroes and its details appear. The application seems to be working again. The heroes appear in a list and details about the clicked hero appear at the bottom of the page.

为什么改好了?

Why it works

selectedHeroundefined 时,ngIf 从 DOM 中移除了英雄详情。因此也就不用关心 selectedHero 的绑定了。

When selectedHero is undefined, the ngIf removes the hero detail from the DOM. There are no selectedHero bindings to consider.

当用户选择一个英雄时,selectedHero 也就有了值,并且 ngIf 把英雄的详情放回到 DOM 中。

When the user picks a hero, selectedHero has a value and ngIf puts the hero detail into the DOM.

为选定的英雄设置样式

Style the selected hero

为了标出选定的英雄,你可以在以前添加过的样式中增加 CSS 类 .selected。若要把 .selected 类应用于此 <li> 上,请使用类绑定。

To help identify the selected hero, you can use the .selected CSS class in the styles you added earlier. To apply the .selected class to the <li> when the user clicks it, use class binding.

Angular 的类绑定可以有条件地添加和删除 CSS 类。只需将 [class.some-css-class]="some-condition" 添加到要设置样式的元素即可。

Angular's class binding can add and remove a CSS class conditionally. Just add [class.some-css-class]="some-condition" to the element you want to style.

HeroesComponent 模板中的 <li> 元素上添加 [class.selected] 绑定,代码如下:

Add the following [class.selected] binding to the <li> in the HeroesComponent template:

heroes.component.html (toggle the 'selected' CSS class)
      
      [class.selected]="hero === selectedHero"
    

如果当前行的英雄和 selectedHero 相同,Angular 就会添加 CSS 类 selected,否则就会移除它。

When the current row hero is the same as the selectedHero, Angular adds the selected CSS class. When the two heroes are different, Angular removes the class.

最终的 <li> 是这样的:

The finished <li> looks like this:

heroes.component.html (list item hero)
      
      <li *ngFor="let hero of heroes"
  [class.selected]="hero === selectedHero"
  (click)="onSelect(hero)">
  <span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
    

查看最终代码

Final code review

下面是本页面中所提及的代码文件,包括 HeroesComponent 的样式。

Here are the code files discussed on this page, including the HeroesComponent styles.

      
      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' }
];
    

小结

Summary

  • 英雄之旅应用在一个主从视图中显示了英雄列表。

    The Tour of Heroes application displays a list of heroes with a detail view.

  • 用户可以选择一个英雄,并查看该英雄的详情。

    The user can select a hero and see that hero's details.

  • 你使用 *ngFor 显示了一个列表。

    You used *ngFor to display a list.

  • 你使用 *ngIf 来根据条件包含或排除了一段 HTML。

    You used *ngIf to conditionally include or exclude a block of HTML.

  • 你可以用 class 绑定来切换 CSS 的样式类。

    You can toggle a CSS style class with a class binding.