英雄指南——主從結(jié)構(gòu)

版本:v4.0.0+2

在這一章,你將擴(kuò)展英雄指南應(yīng)用來(lái)顯示一列英雄,并允許用戶來(lái)選擇一個(gè)英雄,同時(shí)顯示這個(gè)英雄的詳情。

當(dāng)你按照本章完成時(shí),應(yīng)用應(yīng)該看起來(lái)這樣——在線示例 (查看源碼)。

我們離開的地方

在繼續(xù)本章的英雄指南之前,先來(lái)檢查一下是否有下面的結(jié)構(gòu)。如果不是,你得先回到前一章英雄編輯器,看看錯(cuò)過(guò)了什么。

如果應(yīng)用不運(yùn)行了,啟動(dòng)應(yīng)用。當(dāng)你做出修改時(shí),通過(guò)刷新瀏覽器保持繼續(xù)運(yùn)行。

應(yīng)用重構(gòu)

在添加新的應(yīng)用之前,你將從重構(gòu)應(yīng)用標(biāo)題受益。

應(yīng)用模板文件

你將對(duì) app 組件的模板做幾個(gè)更新。首先,移動(dòng)模板到自己的文件:

// lib/app_component.html

<h1>{{title}}</h1>
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div>
  <label>name: </label>
  <input [(ngModel)]="hero.name" placeholder="name">
</div>

使用templateUrl引用新的模板文件替換@Component template

// lib/app_component.dart (metadata)

@Component(
  selector: 'my-app',
  templateUrl: 'app_component.html',
  directives: const [formDirectives],
)

刷新瀏覽器。應(yīng)用仍然運(yùn)行。

英雄類

app_component.dart分離Hero類到它自己的文件。

創(chuàng)建lib/src目錄包含Hero資源:

// lib/src/hero.dart

class Hero {
  final int id;
  String name;

  Hero(this.id, this.name);
}

回到 app 組件,使用相對(duì)路徑添加新創(chuàng)建文件的導(dǎo)入:

// lib/app_component.dart (hero import)

import 'src/hero.dart';

刷新瀏覽器。應(yīng)用仍然運(yùn)行,現(xiàn)在開始準(zhǔn)備添加新的特性。

顯示英雄

要顯示一列英雄,添加 heroes 到視圖模板。

模擬英雄

lib/src目錄下創(chuàng)建如下一個(gè)由十位英雄組成的列表的文件。

// lib/src/mock_heroes.dart

import 'hero.dart';

final mockHeroes = <Hero>[
  new Hero(11, 'Mr. Nice'),
  new Hero(12, 'Narco'),
  new Hero(13, 'Bombasto'),
  new Hero(14, 'Celeritas'),
  new Hero(15, 'Magneta'),
  new Hero(16, 'RubberMan'),
  new Hero(17, 'Dynama'),
  new Hero(18, 'Dr IQ'),
  new Hero(19, 'Magma'),
  new Hero(20, 'Tornado')
];

在最終,應(yīng)用會(huì)從一個(gè) web 服務(wù)器獲取英雄列表,但現(xiàn)在你可以顯示模擬應(yīng)用。

應(yīng)用的 heroes 字段

AppComponent中使用公共的heroes字段替換hero字段,并使用模擬英雄(不要忘記導(dǎo)入)初始化它:

// lib/app_component.dart (heroes)

import 'src/mock_heroes.dart';

// ···
class AppComponent {
  final title = 'Tour of Heroes';
  List<Hero> heroes = mockHeroes;
  // ···
}

英雄數(shù)據(jù)是由分開的不同類實(shí)現(xiàn)的,因?yàn)樽罱K,英雄名將會(huì)來(lái)自服務(wù)器數(shù)據(jù)。

在模板中顯示英雄名

在一個(gè)無(wú)序列表中顯示英雄名,使用下面的 HTML 代替 所有當(dāng)前模板:

// lib/app_component.html (heroes template)

<h1>{{title}}</h1>
<h2>My Heroes</h2>
<ul class="heroes">
  <li>
    <!-- each hero goes here -->
  </li>
</ul>

下一步,你將添加英雄名。

使用 ngFor 來(lái)列出英雄

目標(biāo)是綁定組件的 heroes 列表到模板,迭代它們,并單獨(dú)顯示它們。

修改<li>標(biāo)簽,添加核心指令 *ngFor 。

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

ngFor的前綴星號(hào)(*)是這個(gè)語(yǔ)法的關(guān)鍵部分。它表明<li>元素及其子元素組成了一個(gè)主控模板。

ngFor指令遍歷組件的heroes列表,并按照這個(gè)模板渲染列表中每個(gè)英雄。

表達(dá)式的let hero部分識(shí)別hero為模板輸入變量,為每一個(gè)迭代保存當(dāng)前英雄條目。你可以在模板中引用這個(gè)變量來(lái)訪問(wèn)當(dāng)前英雄的屬性。

更多ngFor和模板輸入變量的內(nèi)容請(qǐng)看顯示數(shù)據(jù)使用 ngFor 顯示屬性列表模板語(yǔ)法ngFor 部分。

<li>元素內(nèi),使用模板變量hero來(lái)添加內(nèi)容,顯示英雄的屬性。

// lib/app_component.html (ngFor)

<li *ngFor="let hero of heroes">
  <span class="badge">{{hero.id}}</span> {{hero.name}}
</li>

要在模板中使用 Angular 指令,需要在組件的@Component注解的directives參數(shù)中列出它們。類似于在前一章做的,添加 CORE_DIRECTIVES:

// lib/app_component.dart (directives)

@Component(
  selector: 'my-app',
  // ···
  directives: const [CORE_DIRECTIVES, formDirectives],
)

刷新瀏覽,一列英雄出現(xiàn)了。

給英雄們添加樣式

用戶應(yīng)該獲取視覺(jué)提示,他們懸停在哪個(gè)英雄上,哪個(gè)英雄被選中。

通過(guò)設(shè)置@Component注解的styles參數(shù),來(lái)給組件添加樣式:

// lib/app_component.dart (styles)

// 當(dāng)添加許多 CSS 類時(shí)不推薦
styles: const [
  '''
    .selected { ... }
    .heroes { ... }
    ...
  '''
],

但是當(dāng)添加許多樣式時(shí),會(huì)使 Dart 文件變長(zhǎng)且難以閱讀。相反,把樣式放到一個(gè).css文件中,然后在@ComponentstyleUrls參數(shù)中引用該文件。按照慣例,組件的 CSS 文件名和 Dart 文件有相同的基礎(chǔ)(app_component)。

//  lib/app_component.css

.selected {
  background-color: #CFD8DC !important;
  color: white;
}
.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.selected:hover {
  color: white;
}
.heroes li:hover {
  color: #607D8B;
  background-color: #EEE;
  left: .1em;
}
.heroes .text {
  position: relative;
  top: -3px;
}
.heroes .badge {
  display: inline-block;
  font-size: small;
  color: white;
  padding: 0.8em 0.7em 0 0.7em;
  background-color: #607D8B;
  line-height: 1em;
  position: relative;
  left: -1px;
  top: -4px;
  height: 1.8em;
  margin-right: .8em;
  border-radius: 4px 0 0 4px;
}

當(dāng)你給組件分配樣式時(shí),它們的作用域?qū)H限于該組件。這些樣式只會(huì)作用于 AppComponent組件,而不會(huì)影響到外部 HTML。

顯示英雄的模板看起來(lái)像這樣:

// lib/app_component.html (styled heroes)

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

選擇英雄

應(yīng)用現(xiàn)在顯示一列英雄,同時(shí)在詳情視圖顯示一個(gè)單獨(dú)的英雄。但列表和詳情視圖之間沒(méi)有關(guān)聯(lián)。當(dāng)用戶從列表選中一個(gè)英雄時(shí),選中的英雄應(yīng)該出現(xiàn)在詳情視圖中。這種 UI 模式被稱為“master/detail.”。在這個(gè)例子中,master 是英雄列表,detail 是被選中的英雄。

接下來(lái),通過(guò)組件的selectedHero屬性來(lái)連接主從視圖,它被綁定到一個(gè)點(diǎn)擊事件。

處理點(diǎn)擊事件

如下添加一個(gè)點(diǎn)擊事件綁定到<li>元素:

// lib/app_component.html (click)

<li *ngFor="let hero of heroes" (click)="onSelect(hero)">
  <span class="badge">{{hero.id}}</span> {{hero.name}}
</li>

圓括號(hào)指定了<li>元素的click事件為目標(biāo)。onSelect(hero)表達(dá)式調(diào)用AppComponentonSelect()方法,它傳遞模板輸入變量hero作為參數(shù)。和之前在ngFor指令中定義的是同一個(gè)hero變量。

學(xué)習(xí)更多關(guān)于事件綁定的內(nèi)容,請(qǐng)看用戶輸入模板語(yǔ)法事件綁定 部分。

添加一個(gè)點(diǎn)擊處理器來(lái)暴露選中的英雄

你不再需要hero屬性,因?yàn)槟悴辉亠@示一個(gè)單獨(dú)的英雄;你顯示一列英雄。但用戶能夠通過(guò)點(diǎn)擊它來(lái)選擇其中一個(gè)英雄。所以使用這個(gè)簡(jiǎn)單的selectedHero屬性代替hero屬性。

// lib/app_component.dart (selectedHero)

Hero selectedHero;

在用戶選擇一個(gè)英雄之前,所有英雄都是未被選中的,所以,你不需要像hero一樣初始化selectedHero

添加一個(gè)onSelect()方法,將用戶點(diǎn)擊的hero賦值給electedHero屬性。

// lib/app_component.dart (onSelect)

void onSelect(Hero hero) => selectedHero = hero;

模板仍然引用舊的hero屬性。如下所示,綁定到新的selectedHero屬性代替它:

// lib/app_component.html (selectedHero details)

<h2>{{selectedHero.name}} details!</h2>
<div><label>id: </label>{{selectedHero.id}}</div>
<div>
  <label>name: </label>
  <input [(ngModel)]="selectedHero.name" placeholder="name">
</div>

使用 ngIf 隱藏空的詳情視圖

當(dāng)應(yīng)用加載時(shí),selectedHero是 null。當(dāng)用戶點(diǎn)擊英雄名字的時(shí)候,selectedHero才會(huì)被初始化。Angular 不能顯示空的selectedHero的屬性,并且拋出如下的錯(cuò)誤,可以在瀏覽器控制臺(tái)中查看:

 EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null]

盡管selectedHero.name顯示在了模板中,但在有一個(gè)選中的英雄之前,英雄詳情不會(huì)出現(xiàn)在 DOM 中。

模板中的英雄詳情 HTML 內(nèi)容使用一個(gè)<div>包裹起來(lái)。然后,添加ngIf核心指令,并把它設(shè)置為selectedHero != null

// lib/app_component.html (ngIf)

<div *ngIf="selectedHero != null">
  <h2>{{selectedHero.name}} details!</h2>
  <div><label>id: </label>{{selectedHero.id}}</div>
  <div>
    <label>name: </label>
    <input [(ngModel)]="selectedHero.name" placeholder="name">
  </div>
</div>

不要忘了 ngIf 的前綴(*)。

刷新瀏覽器。應(yīng)用不再失敗,并且一列名字再次顯示在瀏覽器中。

當(dāng)沒(méi)有選中英雄時(shí),ngIf指令從 DOM 中移除英雄詳情 HTML。沒(méi)有了英雄詳情元素,也就不用擔(dān)心綁定問(wèn)題。

當(dāng)用戶選中一個(gè)英雄,selectedHero不再是nullngIf把英雄詳情添加到 DOM 中,并且對(duì)嵌套的綁定進(jìn)行求值計(jì)算。

更多關(guān)于ngIfngFor的內(nèi)容請(qǐng)看結(jié)構(gòu)指令模板語(yǔ)法內(nèi)置指令 部分。

給選中英雄添加樣式

雖然選中英雄的詳情出現(xiàn)在了列表下面,但很難在列表中識(shí)別出選中的英雄。

在之前添加的styles元數(shù)據(jù)中,有一個(gè)自定義的 CSS 類selected。要使選中英雄更明顯,當(dāng)用戶點(diǎn)擊一個(gè)英雄名時(shí),把selected類應(yīng)用到<li>上。例如,當(dāng)用戶點(diǎn)擊 “Magneta” 時(shí),它應(yīng)該用一個(gè)略有不同的背景色顯示出來(lái),就像這樣:

在模板中,如下所示,添加綁定到<li>標(biāo)簽:

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

當(dāng)表達(dá)式(hero === selectedHero)是true時(shí),Angular 添加selectedCSS 類。當(dāng)表達(dá)式是false,Angular 移除selected類。

===操作符測(cè)試給出的對(duì)象是否完全相等。

更多關(guān)于[class]綁定的內(nèi)容請(qǐng)看模板語(yǔ)法指南。

最終版的<li>看起來(lái)如下:

// lib/app_component.html (ngFor with class.selected)

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

點(diǎn)擊 “Magneta” 后,列表看起來(lái)如下:

回顧應(yīng)用結(jié)構(gòu)

你的應(yīng)用應(yīng)該有如下文件:

教程測(cè)試組件

本教程沒(méi)有包含測(cè)試,如果你查看示例代碼,它有對(duì)本教程添加的每個(gè)新特性的組件測(cè)試。查看 Component Testing 了解更多信息。

你已走過(guò)的路

在本頁(yè)你完成了以下內(nèi)容:

  • 英雄指南應(yīng)用顯示一列可選英雄。
  • 移動(dòng)應(yīng)用的模板到它自己的文件。
  • 移動(dòng)Hero類到lib/src下它自己的文件。
  • 添加選擇英雄并且顯示英雄詳情的功能。
  • 學(xué)習(xí)在組件的模板中,如何使用核心指令ngIfngFor。
  • 在 CSS 文件中定義樣式,并使用它們給應(yīng)用添加樣式。

你的應(yīng)用看起來(lái)應(yīng)該這樣——在線示例 (查看源碼)。

下一步

多組件

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 版本:4.0.0+2 此時(shí)AppComponent做了所有的事情。起初,它顯示一個(gè)單一英雄的詳情。然后,它成了有一...
    soojade閱讀 643評(píng)論 0 1
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,653評(píng)論 19 139
  • Angular 2架構(gòu)總覽 - 簡(jiǎn)書http://www.itdecent.cn/p/aeb11061b82c A...
    葡萄喃喃囈語(yǔ)閱讀 1,550評(píng)論 2 13
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,191評(píng)論 25 708
  • 天本來(lái)就灰蒙蒙的,又不知被誰(shuí)濺上一道惡心的紅顏色。 放學(xué)鈴聲自作多情地嘮叨了好久。 飛奔到校門口才終于聽不見(jiàn)。 路...
    治百病的草閱讀 475評(píng)論 0 1

友情鏈接更多精彩內(nèi)容