阅读(26) (0)

Angular 英雄之旅-显示列表

2022-07-12 17:37:45 更新

显示英雄列表

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

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

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

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

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

在 ​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;
}

使用 *ngFor 列出这些英雄

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

  1. 在顶部添加 ​<h2>​。
  2. 在它下面添加一个 HTML 无序列表 ( ​<ul>​ ) 元素。
  3. 在 ​<ul>​ 中插入 ​<li>​。
  4. 在 ​<li>​ 中放一个 ​<button>​ 元素,以便在 ​<span>​ 元素中显示单个 ​hero ​的属性。
  5. 点缀上一些 CSS 类(稍后你还会添加更多 CSS 样式)。

做完之后应该是这样的:

<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)指令。它会为列表中的每项数据复写它的宿主元素。

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

语法

详情

<li>

宿主元素。

heroes

来自 HeroesComponent 类的存放模拟(mock)英雄的列表。

hero

保存列表每次迭代的当前 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。

查看详情

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

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

添加 click 事件绑定

为 ​<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​)。

添加 click 事件处理器

把该组件的 ​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​ 的样式。

  • src/app/mock-heroes.ts
  • 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' }
    ];
  • src/app/heroes/heroes.component.ts
  • import { Component, OnInit } from '@angular/core';
    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;
      }
    }
  • src/app/heroes/heroes.component.html
  • <h2>My Heroes</h2>
    <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>
  • src/app/heroes/heroes.component.css
  • /* 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 的样式类。