Hero guide 显示列表

您将扩展“英雄指南”应用,让它显示一个英雄列表, 并允许用户选择一个英雄,并查看该英雄的详细信息。

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

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

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

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

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

显示这些英雄

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

Path:"src/app/heroes/heroes.component.ts (import HEROES)"

import { HEROES } from '../mock-heroes';

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

Path:"src/app/heroes/heroes.component.ts"

export class HeroesComponent implements OnInit {

  heroes = HEROES;
}

使用 *ngFor 列出这些英雄

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

  • 在顶部添加 <h2>

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

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

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

完成后如下:

Path:"heroes.component.html (heroes template)"

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

这只展示了一个英雄。要想把他们都列出来,就要为 <li> 添加一个 *ngFor 以便迭代出列表中的所有英雄:

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

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

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

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

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

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

注:

  • ngFor 前面的星号(*)是该语法中的关键部分。

更改列表外观

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

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

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

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

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

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

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

Path:"src/app/heroes/heroes.component.ts (@Component)"

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

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

主从结构

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

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

添加 Click 事件绑定

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

Path:"heroes.component.html (template excerpt)"

<li *ngFor="let hero of heroes" (click)="onSelect(hero)">

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

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

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

添加 click 事件处理器

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

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

Path:"src/app/heroes/heroes.component.ts (onSelect)"

selectedHero: Hero;
onSelect(hero: Hero): void {
  this.selectedHero = hero;
}

添加详情区

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

Path:"heroes.component.html (selected hero details)"

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

刷新浏览器,你会发现应用挂了。

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

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

当应用启动时,selectedHeroundefined,没有问题。

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

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

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

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

*Path:"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>name:
      <input [(ngModel)]="selectedHero.name" placeholder="name"/>
    </label>
  </div>

</div>

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

注:

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

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

给所选英雄添加样式

所有的 <li> 元素看起来都是一样的,因此很难从列表中识别出所选英雄。

如果用户点击了“Magneta”,这个英雄应该用一个略有不同的背景色显示出来,就像这样:

所选英雄的颜色来自于你前面添加的样式中的 CSS 类 .selected。 所以你只要在用户点击一个 <li> 时把 .selected 类应用到该元素上就可以了。

Angular 的 CSS 类绑定机制让根据条件添加或移除一个 CSS 类变得很容易。 只要把 [class.some-css-class]="some-condition" 添加到你要施加样式的元素上就可以了。

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

Path:"heroes.component.html (toggle the 'selected' CSS class)"

[class.selected]="hero === selectedHero"

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

最终的 <li> 是这样的:

Path:"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>

查看最终代码

  1. Path:"src/app/mock-heroes.ts"

    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' }
    ];
  2. Path:"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() {
      }
    
      onSelect(hero: Hero): void {
        this.selectedHero = hero;
      }
    }
  3. Path:"src/app/heroes/heroes.component.html"

    <h2>My Heroes</h2>
    <ul class="heroes">
      <li *ngFor="let hero of heroes"
        [class.selected]="hero === selectedHero"
        (click)="onSelect(hero)">
        <span class="badge">{{hero.id}}</span> {{hero.name}}
      </li>
    </ul>
    
    <div *ngIf="selectedHero">
    
      <h2>{{selectedHero.name | uppercase}} Details</h2>
      <div><span>id: </span>{{selectedHero.id}}</div>
      <div>
        <label>name:
          <input [(ngModel)]="selectedHero.name" placeholder="name"/>
        </label>
      </div>
    
    </div>
  4. Path:"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 {
      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 li.selected {
      background-color: #CFD8DC;
      color: white;
    }
    .heroes li.selected:hover {
      background-color: #BBD8DC;
      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;
      position: relative;
      left: -1px;
      top: -4px;
      height: 1.8em;
      margin-right: .8em;
      border-radius: 4px 0 0 4px;
    }

总结

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

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

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

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

  • 您可以用 class 绑定来切换 CSS 的样式类。
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号

意见反馈
返回顶部