Hero guide 顯示列表

2020-06-29 16:46 更新

您將擴展“英雄指南”應用,讓它顯示一個英雄列表, 并允許用戶選擇一個英雄,并查看該英雄的詳細信息。

創(chuàng)建模擬(mock)的英雄數(shù)據(jù)

您需要一些英雄數(shù)據(jù)以供顯示。

最終,您會從遠端的數(shù)據(jù)服務器獲取它。 不過目前,您要先創(chuàng)建一些模擬的英雄數(shù)據(jù),并假裝它們是從服務器上取到的。

在 "src/app/" 文件夾中創(chuàng)建一個名叫 "mock-heroes.ts" 的文件。 定義一個包含十個英雄的常量數(shù)組 HEROES,并導出它。 該文件是這樣的。

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

  1. import { Hero } from './hero';
  2. export const HEROES: Hero[] = [
  3. { id: 11, name: 'Dr Nice' },
  4. { id: 12, name: 'Narco' },
  5. { id: 13, name: 'Bombasto' },
  6. { id: 14, name: 'Celeritas' },
  7. { id: 15, name: 'Magneta' },
  8. { id: 16, name: 'RubberMan' },
  9. { id: 17, name: 'Dynama' },
  10. { id: 18, name: 'Dr IQ' },
  11. { id: 19, name: 'Magma' },
  12. { id: 20, name: 'Tornado' }
  13. ];

顯示這些英雄

打開 HeroesComponent 類文件,并導入模擬的 HEROES。

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

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

往類中添加一個 heroes 屬性,這樣可以暴露出這個 HEROES 數(shù)組,以供綁定。

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

  1. export class HeroesComponent implements OnInit {
  2. heroes = HEROES;
  3. }

使用 *ngFor 列出這些英雄

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

  • 在頂部添加 <h2>,

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

  • <ul> 中插入一個 <li> 元素,以顯示單個 hero 的屬性。

  • 點綴上一些 CSS 類(稍后你還會添加更多 CSS 樣式)。

完成后如下:

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

  1. <h2>My Heroes</h2>
  2. <ul class="heroes">
  3. <li>
  4. <span class="badge">{{hero.id}}</span> {{hero.name}}
  5. </li>
  6. </ul>

這只展示了一個英雄。要想把他們都列出來,就要為 <li> 添加一個 *ngFor 以便迭代出列表中的所有英雄:

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

*ngFor 是一個 Angular 的復寫器(repeater)指令。 它會為列表中的每項數(shù)據(jù)復寫它的宿主元素。

這個例子中涉及的語法如下:

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

  • heroes 就是來自 HeroesComponent 類的列表。

  • 當依次遍歷這個列表時,hero 會為每個迭代保存當前的英雄對象。

注:
- ngFor 前面的星號(*)是該語法中的關鍵部分。

更改列表外觀

英雄列表應該富有吸引力,并且當用戶把鼠標移到某個英雄上和從列表中選中某個英雄時,應該給出視覺反饋。

在教程的第一章,你曾在 styles.css 中為整個應用設置了一些基礎的樣式。 但那個樣式表并不包含英雄列表所需的樣式。

固然,你可以把更多樣式加入到 styles.css,并且放任它隨著你添加更多組件而不斷膨脹。

但還有更好的方式。你可以定義屬于特定組件的私有樣式,并且讓組件所需的一切(代碼、HTML 和 CSS)都放在一起。

這種方式讓你在其它地方復用該組件更加容易,并且即使全局樣式和這里不一樣,組件也仍然具有期望的外觀。

你可以用多種方式定義私有樣式,或者內(nèi)聯(lián)在 @Component.styles 數(shù)組中,或者在 @Component.styleUrls 所指出的樣式表文件中。

當 CLI 生成 HeroesComponent 時,它也同時為 HeroesComponent 創(chuàng)建了空白的 heroes.component.css 樣式表文件,并且讓 @Component.styleUrls 指向它,像這樣:

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

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

打開 heroes.component.css 文件,并且把 HeroesComponent 的私有 CSS 樣式粘貼進去。 你可以在本指南底部的查看最終代碼中找到它們。

主從結(jié)構(gòu)

當用戶在主列表中點擊一個英雄時,該組件應該在頁面底部顯示所選英雄的詳情。

在本節(jié),你將監(jiān)聽英雄條目的點擊事件,并更新英雄的詳情。

添加 Click 事件綁定

再往 <li> 元素上插入一句點擊事件的綁定代碼:

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

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

這是 Angular 事件綁定 語法的例子。

click 外面的圓括號會讓 Angular 監(jiān)聽這個 <li> 元素的 click 事件。 當用戶點擊 <li> 時,Angular 就會執(zhí)行表達式 onSelect(hero)

下一部分,會在 HeroesComponent 上定義一個 onSelect() 方法,用來顯示 *ngFor 表達式所定義的那個英雄(hero)。

添加 click 事件處理器

把該組件的 hero 屬性改名為 selectedHero,但不要為它賦值。 因為應用剛剛啟動時并沒有所選英雄。

添加如下 onSelect() 方法,它會把模板中被點擊的英雄賦值給組件的 selectedHero 屬性。

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

  1. selectedHero: Hero;
  2. onSelect(hero: Hero): void {
  3. this.selectedHero = hero;
  4. }

添加詳情區(qū)

現(xiàn)在,組件的模板中有一個列表。要想點擊列表中的一個英雄,并顯示該英雄的詳情,你需要在模板中留一個區(qū)域,用來顯示這些詳情。 在 heroes.component.html 中該列表的緊下方,添加如下代碼:

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

  1. <h2>{{selectedHero.name | uppercase}} Details</h2>
  2. <div><span>id: </span>{{selectedHero.id}}</div>
  3. <div>
  4. <label>name:
  5. <input [(ngModel)]="selectedHero.name" placeholder="name"/>
  6. </label>
  7. </div>

刷新瀏覽器,你會發(fā)現(xiàn)應用掛了。

打開瀏覽器的開發(fā)者工具,它的控制臺中顯示出如下錯誤信息:

  1. 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)"**

  1. <div *ngIf="selectedHero">
  2. <h2>{{selectedHero.name | uppercase}} Details</h2>
  3. <div><span>id: </span>{{selectedHero.id}}</div>
  4. <div>
  5. <label>name:
  6. <input [(ngModel)]="selectedHero.name" placeholder="name"/>
  7. </label>
  8. </div>
  9. </div>

瀏覽器刷新之后,英雄名字的列表又出現(xiàn)了。 詳情部分仍然是空。 從英雄列表中點擊一個英雄,它的詳情就出現(xiàn)了。 應用又能工作了。 英雄們出現(xiàn)在列表中,而被點擊的英雄出現(xiàn)在了頁面底部。

注:
- 當 selectedHeroundefined 時,ngIf 從 DOM 中移除了英雄詳情。因此也就不用關心 selectedHero 的綁定了。

  • 當用戶選擇一個英雄時,selectedHero 也就有了值,并且 ngIf 把英雄的詳情放回到 DOM 中。

給所選英雄添加樣式

所有的 <li> 元素看起來都是一樣的,因此很難從列表中識別出所選英雄。

如果用戶點擊了“Magneta”,這個英雄應該用一個略有不同的背景色顯示出來,就像這樣:

所選英雄的顏色來自于你前面添加的樣式中的 CSS 類 .selected。 所以你只要在用戶點擊一個 <li> 時把 .selected 類應用到該元素上就可以了。

Angular 的 CSS 類綁定機制讓根據(jù)條件添加或移除一個 CSS 類變得很容易。 只要把 [class.some-css-class]="some-condition" 添加到你要施加樣式的元素上就可以了。

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

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

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

如果當前行的英雄和 selectedHero 相同,Angular 就會添加 CSS 類 selected,否則就會移除它。

最終的 <li> 是這樣的:

Path:"heroes.component.html (list item hero)"

  1. <li *ngFor="let hero of heroes"
  2. [class.selected]="hero === selectedHero"
  3. (click)="onSelect(hero)">
  4. <span class="badge">{{hero.id}}</span> {{hero.name}}
  5. </li>

查看最終代碼

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

  1. export const HEROES: Hero[] = [
  2. { id: 11, name: 'Dr Nice' },
  3. { id: 12, name: 'Narco' },
  4. { id: 13, name: 'Bombasto' },
  5. { id: 14, name: 'Celeritas' },
  6. { id: 15, name: 'Magneta' },
  7. { id: 16, name: 'RubberMan' },
  8. { id: 17, name: 'Dynama' },
  9. { id: 18, name: 'Dr IQ' },
  10. { id: 19, name: 'Magma' },
  11. { id: 20, name: 'Tornado' }
  12. ];

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

  1. import { Component, OnInit } from '@angular/core';
  2. import { Hero } from '../hero';
  3. import { HEROES } from '../mock-heroes';
  4. @Component({
  5. selector: 'app-heroes',
  6. templateUrl: './heroes.component.html',
  7. styleUrls: ['./heroes.component.css']
  8. })
  9. export class HeroesComponent implements OnInit {
  10. heroes = HEROES;
  11. selectedHero: Hero;
  12. constructor() { }
  13. ngOnInit() {
  14. }
  15. onSelect(hero: Hero): void {
  16. this.selectedHero = hero;
  17. }
  18. }

  1. Path:"src/app/heroes/heroes.component.html"

  1. <h2>My Heroes</h2>
  2. <ul class="heroes">
  3. <li *ngFor="let hero of heroes"
  4. [class.selected]="hero === selectedHero"
  5. (click)="onSelect(hero)">
  6. <span class="badge">{{hero.id}}</span> {{hero.name}}
  7. </li>
  8. </ul>
  9. <div *ngIf="selectedHero">
  10. <h2>{{selectedHero.name | uppercase}} Details</h2>
  11. <div><span>id: </span>{{selectedHero.id}}</div>
  12. <div>
  13. <label>name:
  14. <input [(ngModel)]="selectedHero.name" placeholder="name"/>
  15. </label>
  16. </div>
  17. </div>

  1. Path:"src/app/heroes/heroes.component.css"

  1. /* HeroesComponent's private CSS styles */
  2. .heroes {
  3. margin: 0 0 2em 0;
  4. list-style-type: none;
  5. padding: 0;
  6. width: 15em;
  7. }
  8. .heroes li {
  9. cursor: pointer;
  10. position: relative;
  11. left: 0;
  12. background-color: #EEE;
  13. margin: .5em;
  14. padding: .3em 0;
  15. height: 1.6em;
  16. border-radius: 4px;
  17. }
  18. .heroes li:hover {
  19. color: #607D8B;
  20. background-color: #DDD;
  21. left: .1em;
  22. }
  23. .heroes li.selected {
  24. background-color: #CFD8DC;
  25. color: white;
  26. }
  27. .heroes li.selected:hover {
  28. background-color: #BBD8DC;
  29. color: white;
  30. }
  31. .heroes .badge {
  32. display: inline-block;
  33. font-size: small;
  34. color: white;
  35. padding: 0.8em 0.7em 0 0.7em;
  36. background-color:#405061;
  37. line-height: 1em;
  38. position: relative;
  39. left: -1px;
  40. top: -4px;
  41. height: 1.8em;
  42. margin-right: .8em;
  43. border-radius: 4px 0 0 4px;
  44. }

總結(jié)

  • 英雄指南應用在一個主從視圖中顯示了英雄列表。

  • 用戶可以選擇一個英雄,并查看該英雄的詳情。

  • 您使用 *ngFor 顯示了一個列表。

  • 您使用 *ngIf 來根據(jù)條件包含或排除了一段 HTML。

  • 您可以用 class 綁定來切換 CSS 的樣式類。
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號