您將擴(kuò)展“英雄指南”應(yīng)用,讓它顯示一個(gè)英雄列表, 并允許用戶選擇一個(gè)英雄,并查看該英雄的詳細(xì)信息。
您需要一些英雄數(shù)據(jù)以供顯示。
最終,您會(huì)從遠(yuǎn)端的數(shù)據(jù)服務(wù)器獲取它。 不過目前,您要先創(chuàng)建一些模擬的英雄數(shù)據(jù),并假裝它們是從服務(wù)器上取到的。
在 "src/app/" 文件夾中創(chuàng)建一個(gè)名叫 "mock-heroes.ts" 的文件。 定義一個(gè)包含十個(gè)英雄的常量數(shù)組 HEROES
,并導(dǎo)出它。 該文件是這樣的。
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 類文件,并導(dǎo)入模擬的 HEROES
。
Path:"src/app/heroes/heroes.component.ts (import HEROES)"
import { HEROES } from '../mock-heroes';
往類中添加一個(gè) heroes
屬性,這樣可以暴露出這個(gè) HEROES
數(shù)組,以供綁定。
Path:"src/app/heroes/heroes.component.ts"
export class HeroesComponent implements OnInit {
heroes = HEROES;
}
*ngFor
列出這些英雄打開 HeroesComponent 的模板文件,并做如下修改:
<h2>
,<ul>
)<ul>
中插入一個(gè) <li>
元素,以顯示單個(gè) hero
的屬性。完成后如下:
Path:"heroes.component.html (heroes template)"
<h2>My Heroes</h2>
<ul class="heroes">
<li>
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
這只展示了一個(gè)英雄。要想把他們都列出來(lái),就要為 <li
> 添加一個(gè) *ngFor
以便迭代出列表中的所有英雄:
<li *ngFor="let hero of heroes">
*ngFor
是一個(gè) Angular 的復(fù)寫器(repeater)指令。 它會(huì)為列表中的每項(xiàng)數(shù)據(jù)復(fù)寫它的宿主元素。
這個(gè)例子中涉及的語(yǔ)法如下:
<li>
就是 *ngFor
的宿主元素。heroes
就是來(lái)自 HeroesComponent 類的列表。hero
會(huì)為每個(gè)迭代保存當(dāng)前的英雄對(duì)象。注:
- ngFor 前面的星號(hào)(*)是該語(yǔ)法中的關(guān)鍵部分。
英雄列表應(yīng)該富有吸引力,并且當(dāng)用戶把鼠標(biāo)移到某個(gè)英雄上和從列表中選中某個(gè)英雄時(shí),應(yīng)該給出視覺反饋。
在教程的第一章,你曾在 styles.css 中為整個(gè)應(yīng)用設(shè)置了一些基礎(chǔ)的樣式。 但那個(gè)樣式表并不包含英雄列表所需的樣式。
固然,你可以把更多樣式加入到 styles.css,并且放任它隨著你添加更多組件而不斷膨脹。
但還有更好的方式。你可以定義屬于特定組件的私有樣式,并且讓組件所需的一切(代碼、HTML 和 CSS)都放在一起。
這種方式讓你在其它地方復(fù)用該組件更加容易,并且即使全局樣式和這里不一樣,組件也仍然具有期望的外觀。
你可以用多種方式定義私有樣式,或者內(nèi)聯(lián)在 @Component.styles
數(shù)組中,或者在 @Component.styleUrls
所指出的樣式表文件中。
當(dāng) CLI 生成 HeroesComponent 時(shí),它也同時(shí)為 HeroesComponent 創(chuàng)建了空白的 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 樣式粘貼進(jìn)去。 你可以在本指南底部的查看最終代碼中找到它們。
當(dāng)用戶在主列表中點(diǎn)擊一個(gè)英雄時(shí),該組件應(yīng)該在頁(yè)面底部顯示所選英雄的詳情。
在本節(jié),你將監(jiān)聽英雄條目的點(diǎn)擊事件,并更新英雄的詳情。
再往 <li> 元素上插入一句點(diǎn)擊事件的綁定代碼:
Path:"heroes.component.html (template excerpt)"
<li *ngFor="let hero of heroes" (click)="onSelect(hero)">
這是 Angular 事件綁定 語(yǔ)法的例子。
click 外面的圓括號(hào)會(huì)讓 Angular 監(jiān)聽這個(gè) <li>
元素的 click 事件。 當(dāng)用戶點(diǎn)擊 <li>
時(shí),Angular 就會(huì)執(zhí)行表達(dá)式 onSelect(hero)
。
下一部分,會(huì)在 HeroesComponent 上定義一個(gè) onSelect()
方法,用來(lái)顯示 *ngFor
表達(dá)式所定義的那個(gè)英雄(hero)。
把該組件的 hero
屬性改名為 selectedHero
,但不要為它賦值。 因?yàn)閼?yīng)用剛剛啟動(dòng)時(shí)并沒有所選英雄。
添加如下 onSelect()
方法,它會(huì)把模板中被點(diǎn)擊的英雄賦值給組件的 selectedHero
屬性。
Path:"src/app/heroes/heroes.component.ts (onSelect)"
selectedHero: Hero;
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
現(xiàn)在,組件的模板中有一個(gè)列表。要想點(diǎn)擊列表中的一個(gè)英雄,并顯示該英雄的詳情,你需要在模板中留一個(gè)區(qū)域,用來(lái)顯示這些詳情。 在 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>
刷新瀏覽器,你會(huì)發(fā)現(xiàn)應(yīng)用掛了。
打開瀏覽器的開發(fā)者工具,它的控制臺(tái)中顯示出如下錯(cuò)誤信息:
HeroesComponent.html:3 ERROR TypeError: Cannot read property 'name' of undefined
當(dāng)應(yīng)用啟動(dòng)時(shí),selectedHero
是 undefined
,沒有問題。
但模板中的綁定表達(dá)式引用了 selectedHero
的屬性(表達(dá)式為 {{selectedHero.name}}
),這必然會(huì)失敗,因?yàn)槟氵€沒選過英雄呢。
*ngIf
隱藏空白的詳情
該組件應(yīng)該只有當(dāng) selectedHero
存在時(shí)才顯示所選英雄的詳情。
把顯示英雄詳情的 HTML 包裹在一個(gè) <div>
中。 并且為這個(gè) div
添加 Angular 的 *ngIf
指令,把它的值設(shè)置為 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>
瀏覽器刷新之后,英雄名字的列表又出現(xiàn)了。 詳情部分仍然是空。 從英雄列表中點(diǎn)擊一個(gè)英雄,它的詳情就出現(xiàn)了。 應(yīng)用又能工作了。 英雄們出現(xiàn)在列表中,而被點(diǎn)擊的英雄出現(xiàn)在了頁(yè)面底部。
注:
- 當(dāng)selectedHero
為undefined
時(shí),ngIf
從 DOM 中移除了英雄詳情。因此也就不用關(guān)心selectedHero
的綁定了。
- 當(dāng)用戶選擇一個(gè)英雄時(shí),
selectedHero
也就有了值,并且ngIf
把英雄的詳情放回到 DOM 中。
所有的 <li>
元素看起來(lái)都是一樣的,因此很難從列表中識(shí)別出所選英雄。
如果用戶點(diǎn)擊了“Magneta”,這個(gè)英雄應(yīng)該用一個(gè)略有不同的背景色顯示出來(lái),就像這樣:
所選英雄的顏色來(lái)自于你前面添加的樣式中的 CSS 類 .selected
。 所以你只要在用戶點(diǎn)擊一個(gè) <li>
時(shí)把 .selected
類應(yīng)用到該元素上就可以了。
Angular 的 CSS 類綁定機(jī)制讓根據(jù)條件添加或移除一個(gè) CSS 類變得很容易。 只要把 [class.some-css-class]="some-condition"
添加到你要施加樣式的元素上就可以了。
在 HeroesComponent 模板中的 <li>
元素上添加 [class.selected]
綁定,代碼如下:
Path:"heroes.component.html (toggle the 'selected' CSS class)"
[class.selected]="hero === selectedHero"
如果當(dāng)前行的英雄和 selectedHero
相同,Angular 就會(huì)添加 CSS 類 selected
,否則就會(huì)移除它。
最終的 <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>
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' }
];
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;
}
}
<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>
/* 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
顯示了一個(gè)列表。*ngIf
來(lái)根據(jù)條件包含或排除了一段 HTML。
更多建議: