假設(shè)有一些“英雄指南”的新需求:
完成效果:
在 Angular 中,最好在一個(gè)獨(dú)立的頂層模塊中加載和配置路由器,它專(zhuān)注于路由功能,然后由根模塊 AppModule
導(dǎo)入它。
按照慣例,這個(gè)模塊類(lèi)的名字叫做 AppRoutingModule
,并且位于 "src/app" 下的 "app-routing.module.ts" 文件中。
使用 CLI 生成它。
ng generate module app-routing --flat --module=app
注:
- --flat 把這個(gè)文件放進(jìn)了 src/app 中,而不是單獨(dú)的目錄中。
- --module=app 告訴 CLI 把它注冊(cè)到 AppModule 的 imports 數(shù)組中。
生成文件是這樣的:
Path:"src/app/app-routing.module.ts (generated)"
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
imports: [
CommonModule
],
declarations: []
})
export class AppRoutingModule { }
把它替換如下:
Path:"src/app/app-routing.module.ts (updated)"
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HeroesComponent } from './heroes/heroes.component';
const routes: Routes = [
{ path: 'heroes', component: HeroesComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
首先,AppRoutingModule 會(huì)導(dǎo)入 RouterModule 和 Routes,以便該應(yīng)用具有路由功能。配置好路由后,接著導(dǎo)入 HeroesComponent,它將告訴路由器要去什么地方。
注意,對(duì) CommonModule 的引用和 declarations 數(shù)組不是必要的,因此它們不再是 AppRoutingModule 的一部分。以下各節(jié)將詳細(xì)介紹 AppRoutingModule 的其余部分。
該文件的下一部分是你的路由配置。 Routes 告訴路由器,當(dāng)用戶(hù)單擊鏈接或?qū)?URL 粘貼進(jìn)瀏覽器地址欄時(shí)要顯示哪個(gè)視圖。
由于 AppRoutingModule 已經(jīng)導(dǎo)入了 HeroesComponent,因此你可以直接在 routes 數(shù)組中使用它:
Path:"src/app/app-routing.module.ts"
const routes: Routes = [
{ path: 'heroes', component: HeroesComponent }
];
典型的 Angular Route 具有兩個(gè)屬性:
path
: 用來(lái)匹配瀏覽器地址欄中 URL 的字符串。
component
: 導(dǎo)航到該路由時(shí),路由器應(yīng)該創(chuàng)建的組件。
這會(huì)告訴路由器把該 URL 與 path:'heroes' 匹配。 如果網(wǎng)址類(lèi)似于 "localhost:4200/heroes" 就顯示 HeroesComponent。
@NgModule
元數(shù)據(jù)會(huì)初始化路由器,并開(kāi)始監(jiān)聽(tīng)瀏覽器地址的變化。
下面的代碼行將 RouterModule
添加到 AppRoutingModule 的 imports
數(shù)組中,同時(shí)通過(guò)調(diào)用 RouterModule.forRoot()
來(lái)用這些 routes 配置它:
Path:"src/app/app-routing.module.ts"
imports: [ RouterModule.forRoot(routes) ],
注:
- 這個(gè)方法之所以叫 forRoot(),是因?yàn)槟阋趹?yīng)用的頂層配置這個(gè)路由器。 forRoot() 方法會(huì)提供路由所需的服務(wù)提供者和指令,還會(huì)基于瀏覽器的當(dāng)前 URL 執(zhí)行首次導(dǎo)航。
接下來(lái),AppRoutingModule
導(dǎo)出 RouterModule
,以便它在整個(gè)應(yīng)用程序中生效。
Path:"src/app/app-routing.module.ts (exports array)"
exports: [ RouterModule ]
打開(kāi) AppComponent 的模板,把 <app-heroes>
元素替換為 <router-outlet>
元素。
Path:"src/app/app.component.html (router-outlet)"
<h1>{{title}}</h1>
<router-outlet></router-outlet>
<app-messages></app-messages>
AppComponent 的模板不再需要 <app-heroes>
,因?yàn)橹挥挟?dāng)用戶(hù)導(dǎo)航到這里時(shí),才需要顯示 HeroesComponent。
<router-outlet>
會(huì)告訴路由器要在哪里顯示路由的視圖。
注:
- 能在 AppComponent 中使用 RouterOutlet,是因?yàn)?AppModule 導(dǎo)入了 AppRoutingModule,而 AppRoutingModule 中導(dǎo)出了 RouterModule。 在本教程開(kāi)始時(shí)你運(yùn)行的那個(gè) ng generate 命令添加了這個(gè)導(dǎo)入,是因?yàn)?--module=app 標(biāo)志。如果你手動(dòng)創(chuàng)建 app-routing.module.ts 或使用了 CLI 之外的工具,你就要把 AppRoutingModule 導(dǎo)入到 app.module.ts 中,并且把它添加到 NgModule 的 imports 數(shù)組中。
此時(shí)瀏覽器應(yīng)該刷新,并顯示應(yīng)用標(biāo)題,但是沒(méi)有顯示英雄列表。
看看瀏覽器的地址欄。 URL 是以 /
結(jié)尾的。 而到 HeroesComponent 的路由路徑是 /heroes
。
在地址欄中把 /heroes
追加到 URL 后面。你應(yīng)該能看到熟悉的主從結(jié)構(gòu)的英雄顯示界面。
理想情況下,用戶(hù)應(yīng)該能通過(guò)點(diǎn)擊鏈接進(jìn)行導(dǎo)航,而不用被迫把路由的 URL 粘貼到地址欄。
添加一個(gè) <nav>
元素,并在其中放一個(gè)鏈接 <a>
元素,當(dāng)點(diǎn)擊它時(shí),就會(huì)觸發(fā)一個(gè)到 HeroesComponent 的導(dǎo)航。 修改過(guò)的 AppComponent 模板如下:
Path:"src/app/app.component.html (heroes RouterLink)"
<h1>{{title}}</h1>
<nav>
<a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
<app-messages></app-messages>
routerLink
屬性的值為 "/heroes"
,路由器會(huì)用它來(lái)匹配出指向 HeroesComponent 的路由。 routerLink
是 RouterLink
指令的選擇器,它會(huì)把用戶(hù)的點(diǎn)擊轉(zhuǎn)換為路由器的導(dǎo)航操作。 它是 RouterModule
中的另一個(gè)公共指令。
刷新瀏覽器,顯示出了應(yīng)用的標(biāo)題和指向英雄列表的鏈接,但并沒(méi)有顯示英雄列表。
點(diǎn)擊這個(gè)鏈接。地址欄變成了 /heroes
,并且顯示出了英雄列表。
注:
- 從下面的 最終代碼中把私有 CSS 樣式添加到 app.component.css 中,可以讓導(dǎo)航鏈接變得更好看一點(diǎn)。
當(dāng)有多個(gè)視圖時(shí),路由會(huì)更有價(jià)值。不過(guò)目前還只有一個(gè)英雄列表視圖。
使用 CLI 添加一個(gè) DashboardComponent:
ng generate component dashboard
CLI 生成了 DashboardComponent 的相關(guān)文件,并把它聲明到 AppModule
中。
把這三個(gè)文件中的內(nèi)容改成這樣:
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<a *ngFor="let hero of heroes" class="col-1-4">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</a>
</div>
import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: [ './dashboard.component.css' ]
})
export class DashboardComponent implements OnInit {
heroes: Hero[] = [];
constructor(private heroService: HeroService) { }
ngOnInit() {
this.getHeroes();
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes.slice(1, 5));
}
}
/* DashboardComponent's private CSS styles */
[class*='col-'] {
float: left;
padding-right: 20px;
padding-bottom: 20px;
}
[class*='col-']:last-of-type {
padding-right: 0;
}
a {
text-decoration: none;
}
*, *:after, *:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
h3 {
text-align: center;
margin-bottom: 0;
}
h4 {
position: relative;
}
.grid {
margin: 0;
}
.col-1-4 {
width: 25%;
}
.module {
padding: 20px;
text-align: center;
color: #eee;
max-height: 120px;
min-width: 120px;
background-color: #3f525c;
border-radius: 2px;
}
.module:hover {
background-color: #eee;
cursor: pointer;
color: #607d8b;
}
.grid-pad {
padding: 10px 0;
}
.grid-pad > [class*='col-']:last-of-type {
padding-right: 20px;
}
@media (max-width: 600px) {
.module {
font-size: 10px;
max-height: 75px; }
}
@media (max-width: 1024px) {
.grid {
margin: 0;
}
.module {
min-width: 60px;
}
}
這個(gè)模板用來(lái)表示由英雄名字鏈接組成的一個(gè)陣列。
*ngFor
復(fù)寫(xiě)器為組件的 heroes
數(shù)組中的每個(gè)條目創(chuàng)建了一個(gè)鏈接。
這些鏈接被 dashboard.component.css 中的樣式格式化成了一些色塊。
這些鏈接還沒(méi)有指向任何地方,但很快就會(huì)了。
這個(gè)類(lèi)和 HeroesComponent
類(lèi)很像。
它定義了一個(gè) heroes
數(shù)組屬性。
它的構(gòu)造函數(shù)希望 Angular 把 HeroService 注入到私有的 heroService
屬性中。
在 ngOnInit()
生命周期鉤子中調(diào)用 getHeroes()
。
這個(gè) getHeroes()
函數(shù)會(huì)截取第 2 到 第 5 位英雄,也就是說(shuō)只返回四個(gè)頂層英雄(第二,第三,第四和第五)。
Path:"src/app/dashboard/dashboard.component.ts"
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes.slice(1, 5));
}
要導(dǎo)航到儀表盤(pán),路由器中就需要一個(gè)相應(yīng)的路由。
把 DashboardComponent 導(dǎo)入到 AppRoutingModule
中。
Path:"src/app/app-routing.module.ts (import DashboardComponent)"
import { DashboardComponent } from './dashboard/dashboard.component';
把一個(gè)指向 DashboardComponent 的路由添加到 AppRoutingModule.routes 數(shù)組中。
Path:"src/app/app-routing.module.ts"
{ path: 'dashboard', component: DashboardComponent },
當(dāng)應(yīng)用啟動(dòng)時(shí),瀏覽器的地址欄指向了網(wǎng)站的根路徑。 它沒(méi)有匹配到任何現(xiàn)存路由,因此路由器也不會(huì)導(dǎo)航到任何地方。 <router-outlet>
下方是空白的。
要讓?xiě)?yīng)用自動(dòng)導(dǎo)航到這個(gè)儀表盤(pán),請(qǐng)把下列路由添加到 AppRoutingModule.Routes
數(shù)組中。
Path:"src/app/app-routing.module.ts"
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
這個(gè)路由會(huì)把一個(gè)與空路徑“完全匹配”的 URL 重定向到路徑為 '/dashboard'
的路由。
瀏覽器刷新之后,路由器加載了 DashboardComponent,并且瀏覽器的地址欄會(huì)顯示出 /dashboard
這個(gè) URL。
應(yīng)該允許用戶(hù)通過(guò)點(diǎn)擊頁(yè)面頂部導(dǎo)航區(qū)的各個(gè)鏈接在 DashboardComponent 和 HeroesComponent 之間來(lái)回導(dǎo)航。
把儀表盤(pán)的導(dǎo)航鏈接添加到殼組件 AppComponent 的模板中,就放在 Heroes
鏈接的前面。
Path:"src/app/app.component.html"
<h1>{{title}}</h1>
<nav>
<a routerLink="/dashboard">Dashboard</a>
<a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
<app-messages></app-messages>
刷新瀏覽器,你就能通過(guò)點(diǎn)擊這些鏈接在這兩個(gè)視圖之間自由導(dǎo)航了。
HeroDetailComponent 可以顯示所選英雄的詳情。 此刻,HeroDetailsComponent 只能在 HeroesComponent 的底部看到。
用戶(hù)應(yīng)該能通過(guò)三種途徑看到這些詳情。
通過(guò)在儀表盤(pán)中點(diǎn)擊某個(gè)英雄。
通過(guò)在英雄列表中點(diǎn)擊某個(gè)英雄。
通過(guò)把一個(gè)“深鏈接” URL 粘貼到瀏覽器的地址欄中來(lái)指定要顯示的英雄。
在這一節(jié),你將能導(dǎo)航到 HeroDetailComponent,并把它從 HeroesComponent 中解放出來(lái)。
當(dāng)用戶(hù)在 HeroesComponent 中點(diǎn)擊某個(gè)英雄條目時(shí),應(yīng)用應(yīng)該能導(dǎo)航到 HeroDetailComponent,從英雄列表視圖切換到英雄詳情視圖。 英雄列表視圖將不再顯示,而英雄詳情視圖要顯示出來(lái)。
打開(kāi) HeroesComponent 的模板文件(heroes/heroes.component.html),并從底部刪除 <app-hero-detail>
元素。
目前,點(diǎn)擊某個(gè)英雄條目還沒(méi)有反應(yīng)。不過(guò)當(dāng)你啟用了到 HeroDetailComponent 的路由之后,很快就能修復(fù)它。
要導(dǎo)航到 id
為 11 的英雄的詳情視圖,類(lèi)似于 ~/detail/11
的 URL 將是一個(gè)不錯(cuò)的 URL。
打開(kāi) AppRoutingModule
并導(dǎo)入 HeroDetailComponent
。
Path:"src/app/app-routing.module.ts (import HeroDetailComponent)"
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
然后把一個(gè)參數(shù)化路由添加到 AppRoutingModule.routes
數(shù)組中,它要匹配指向英雄詳情視圖的路徑。
Path:"src/app/app-routing.module.ts"
{ path: 'detail/:id', component: HeroDetailComponent },
path 中的冒號(hào)(:
)表示 :id
是一個(gè)占位符,它表示某個(gè)特定英雄的 id
。
此刻,應(yīng)用中的所有路由都就緒了。
Path:"src/app/app-routing.module.ts (all routes)"
const routes: Routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent },
{ path: 'detail/:id', component: HeroDetailComponent },
{ path: 'heroes', component: HeroesComponent }
];
此刻,DashboardComponent 中的英雄連接還沒(méi)有反應(yīng)。
路由器已經(jīng)有一個(gè)指向 HeroDetailComponent 的路由了, 修改儀表盤(pán)中的英雄連接,讓它們通過(guò)參數(shù)化的英雄詳情路由進(jìn)行導(dǎo)航。
Path:"src/app/dashboard/dashboard.component.html (hero links))"
<a *ngFor="let hero of heroes" class="col-1-4"
routerLink="/detail/{{hero.id}}">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</a>
你正在 *ngFor
復(fù)寫(xiě)器中使用 Angular 的插值綁定來(lái)把當(dāng)前迭代的 hero.id
插入到每個(gè) routerLink
中。
HeroesComponent 中的這些英雄條目都是 <li>
元素,它們的點(diǎn)擊事件都綁定到了組件的 onSelect()
方法中。
Path:"src/app/heroes/heroes.component.html (list with onSelect)"
<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>
清理 <li>
,只保留它的 *ngFor
,把徽章(<badge>
)和名字包裹進(jìn)一個(gè) <a>
元素中, 并且像儀表盤(pán)的模板中那樣為這個(gè) <a>
元素添加一個(gè) routerLink
屬性。
Path:"src/app/heroes/heroes.component.html (list with links)"
<ul class="heroes">
<li *ngFor="let hero of heroes">
<a routerLink="/detail/{{hero.id}}">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</a>
</li>
</ul>
你還要修改私有樣式表(heroes.component.css),讓列表恢復(fù)到以前的外觀。 修改后的樣式表參見(jiàn)本指南底部的最終代碼。
雖然 HeroesComponent 類(lèi)仍然能正常工作,但 onSelect()
方法和 selectedHero
屬性已經(jīng)沒(méi)用了。
最好清理掉它們,將來(lái)你會(huì)體會(huì)到這么做的好處。 下面是刪除了死代碼之后的類(lèi)。
Path:"src/app/heroes/heroes.component.ts (cleaned up)"
export class HeroesComponent implements OnInit {
heroes: Hero[];
constructor(private heroService: HeroService) { }
ngOnInit() {
this.getHeroes();
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
}
以前,父組件 HeroesComponent 會(huì)設(shè)置 HeroDetailComponent.hero
屬性,然后 HeroDetailComponent 就會(huì)顯示這個(gè)英雄。
HeroesComponent 已經(jīng)不會(huì)再那么做了。 現(xiàn)在,當(dāng)路由器會(huì)在響應(yīng)形如 ~/detail/11
的 URL 時(shí)創(chuàng)建 HeroDetailComponent。
HeroDetailComponent 需要從一種新的途徑獲取要顯示的英雄。 本節(jié)會(huì)講解如下操作:
id
。id
的英雄數(shù)據(jù)。先添加下列導(dǎo)入語(yǔ)句:
Path:"src/app/hero-detail/hero-detail.component.ts"
import { ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common';
import { HeroService } from '../hero.service';
然后把 ActivatedRoute、HeroService 和 Location 服務(wù)注入到構(gòu)造函數(shù)中,將它們的值保存到私有變量里:
Path:"src/app/hero-detail/hero-detail.component.ts"
constructor(
private route: ActivatedRoute,
private heroService: HeroService,
private location: Location
) {}
ActivatedRoute 保存著到這個(gè) HeroDetailComponent 實(shí)例的路由信息。 這個(gè)組件對(duì)從 URL 中提取的路由參數(shù)感興趣。 其中的 id
參數(shù)就是要顯示的英雄的 id
。
HeroService 從遠(yuǎn)端服務(wù)器獲取英雄數(shù)據(jù),本組件將使用它來(lái)獲取要顯示的英雄。
location 是一個(gè) Angular 的服務(wù),用來(lái)與瀏覽器打交道。 稍后,你就會(huì)使用它來(lái)導(dǎo)航回上一個(gè)視圖。
在 ngOnInit()
生命周期鉤子 中調(diào)用 getHero()
,代碼如下:
Path:"src/app/hero-detail/hero-detail.component.ts"
ngOnInit(): void {
this.getHero();
}
getHero(): void {
const id = +this.route.snapshot.paramMap.get('id');
this.heroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
route.snapshot 是一個(gè)路由信息的靜態(tài)快照,抓取自組件剛剛創(chuàng)建完畢之后。
paramMap 是一個(gè)從 URL 中提取的路由參數(shù)值的字典。 "id" 對(duì)應(yīng)的值就是要獲取的英雄的 id。
路由參數(shù)總會(huì)是字符串。 JavaScript 的 (+
) 操作符會(huì)把字符串轉(zhuǎn)換成數(shù)字,英雄的 id 就是數(shù)字類(lèi)型。
刷新瀏覽器,應(yīng)用掛了。出現(xiàn)一個(gè)編譯錯(cuò)誤,因?yàn)?HeroService 沒(méi)有一個(gè)名叫 getHero()
的方法。 這就添加它。
添加 HeroService,并在 getHeroes()
后面添加如下的 getHero()
方法,它接收 id 參數(shù):
Path:"src/app/hero.service.ts (getHero) "
getHero(id: number): Observable<Hero> {
// TODO: send the message _after_ fetching the hero
this.messageService.add(`HeroService: fetched hero id=${id}`);
return of(HEROES.find(hero => hero.id === id));
}
注:
- 反引號(hào) ( ` ) 用于定義 JavaScript 的 模板字符串字面量,以便嵌入 id。
像 getHeroes()
一樣,getHero()
也有一個(gè)異步函數(shù)簽名。 它用 RxJS 的 of()
函數(shù)返回一個(gè) Observable 形式的模擬英雄數(shù)據(jù)。
你將來(lái)可以用一個(gè)真實(shí)的 Http 請(qǐng)求來(lái)重新實(shí)現(xiàn) getHero()
,而不用修改調(diào)用了它的 HeroDetailComponent。
此時(shí)刷新瀏覽器,應(yīng)用再次恢復(fù)如常。你可以在儀表盤(pán)或英雄列表中點(diǎn)擊一個(gè)英雄來(lái)導(dǎo)航到該英雄的詳情視圖。
如果你在瀏覽器的地址欄中粘貼了 "localhost:4200/detail/11",路由器也會(huì)導(dǎo)航到 id: 11 的英雄("Dr. Nice")的詳情視圖。
通過(guò)點(diǎn)擊瀏覽器的后退按鈕,你可以回到英雄列表或儀表盤(pán)視圖,這取決于你從哪里進(jìn)入的詳情視圖。
如果能在 HeroDetail 視圖中也有這么一個(gè)按鈕就更好了。
把一個(gè)后退按鈕添加到組件模板的底部,并且把它綁定到組件的 goBack()
方法。
Path:"src/app/hero-detail/hero-detail.component.html (back button)"
<button (click)="goBack()">go back</button>
在組件類(lèi)中添加一個(gè) goBack()
方法,利用你以前注入的 Location 服務(wù)在瀏覽器的歷史棧中后退一步。
Path:"src/app/hero-detail/hero-detail.component.ts (goBack)"
goBack(): void {
this.location.back();
}
刷新瀏覽器,并開(kāi)始點(diǎn)擊。 用戶(hù)能在應(yīng)用中導(dǎo)航:從儀表盤(pán)到英雄詳情再回來(lái),從英雄列表到 mini 版英雄詳情到英雄詳情,再回到英雄列表。
Path:"src/app/app-routing.module.ts"
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DashboardComponent } from './dashboard/dashboard.component';
import { HeroesComponent } from './heroes/heroes.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
const routes: Routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent },
{ path: 'detail/:id', component: HeroDetailComponent },
{ path: 'heroes', component: HeroesComponent }
];
@NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule {}
Path:"src/app/app.module.ts"
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
import { HeroesComponent } from './heroes/heroes.component';
import { MessagesComponent } from './messages/messages.component';
import { AppRoutingModule } from './app-routing.module';
@NgModule({
imports: [
BrowserModule,
FormsModule,
AppRoutingModule
],
declarations: [
AppComponent,
DashboardComponent,
HeroesComponent,
HeroDetailComponent,
MessagesComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Path:"src/app/hero.service.ts"
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
import { MessageService } from './message.service';
@Injectable({ providedIn: 'root' })
export class HeroService {
constructor(private messageService: MessageService) { }
getHeroes(): Observable<Hero[]> {
// TODO: send the message _after_ fetching the heroes
this.messageService.add('HeroService: fetched heroes');
return of(HEROES);
}
getHero(id: number): Observable<Hero> {
// TODO: send the message _after_ fetching the hero
this.messageService.add(`HeroService: fetched hero id=${id}`);
return of(HEROES.find(hero => hero.id === id));
}
}
<h1>{{title}}</h1>
<nav>
<a routerLink="/dashboard">Dashboard</a>
<a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
<app-messages></app-messages>
/* AppComponent's private CSS styles */
h1 {
font-size: 1.2em;
margin-bottom: 0;
}
h2 {
font-size: 2em;
margin-top: 0;
padding-top: 0;
}
nav a {
padding: 5px 10px;
text-decoration: none;
margin-top: 10px;
display: inline-block;
background-color: #eee;
border-radius: 4px;
}
nav a:visited, a:link {
color: #334953;
}
nav a:hover {
color: #039be5;
background-color: #cfd8dc;
}
nav a.active {
color: #039be5;
}
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<a *ngFor="let hero of heroes" class="col-1-4"
routerLink="/detail/{{hero.id}}">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</a>
</div>
import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: [ './dashboard.component.css' ]
})
export class DashboardComponent implements OnInit {
heroes: Hero[] = [];
constructor(private heroService: HeroService) { }
ngOnInit() {
this.getHeroes();
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes.slice(1, 5));
}
}
/* DashboardComponent's private CSS styles */
[class*='col-'] {
float: left;
padding-right: 20px;
padding-bottom: 20px;
}
[class*='col-']:last-of-type {
padding-right: 0;
}
a {
text-decoration: none;
}
*, *:after, *:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
h3 {
text-align: center;
margin-bottom: 0;
}
h4 {
position: relative;
}
.grid {
margin: 0;
}
.col-1-4 {
width: 25%;
}
.module {
padding: 20px;
text-align: center;
color: #eee;
max-height: 120px;
min-width: 120px;
background-color: #3f525c;
border-radius: 2px;
}
.module:hover {
background-color: #eee;
cursor: pointer;
color: #607d8b;
}
.grid-pad {
padding: 10px 0;
}
.grid-pad > [class*='col-']:last-of-type {
padding-right: 20px;
}
@media (max-width: 600px) {
.module {
font-size: 10px;
max-height: 75px; }
}
@media (max-width: 1024px) {
.grid {
margin: 0;
}
.module {
min-width: 60px;
}
}
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes">
<a routerLink="/detail/{{hero.id}}">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</a>
</li>
</ul>
import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
heroes: Hero[];
constructor(private heroService: HeroService) { }
ngOnInit() {
this.getHeroes();
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
}
/* DashboardComponent's private CSS styles */
[class*='col-'] {
float: left;
padding-right: 20px;
padding-bottom: 20px;
}
[class*='col-']:last-of-type {
padding-right: 0;
}
a {
text-decoration: none;
}
*, *:after, *:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
h3 {
text-align: center;
margin-bottom: 0;
}
h4 {
position: relative;
}
.grid {
margin: 0;
}
.col-1-4 {
width: 25%;
}
.module {
padding: 20px;
text-align: center;
color: #eee;
max-height: 120px;
min-width: 120px;
background-color: #3f525c;
border-radius: 2px;
}
.module:hover {
background-color: #eee;
cursor: pointer;
color: #607d8b;
}
.grid-pad {
padding: 10px 0;
}
.grid-pad > [class*='col-']:last-of-type {
padding-right: 20px;
}
@media (max-width: 600px) {
.module {
font-size: 10px;
max-height: 75px; }
}
@media (max-width: 1024px) {
.grid {
margin: 0;
}
.module {
min-width: 60px;
}
}
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes">
<a routerLink="/detail/{{hero.id}}">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</a>
</li>
</ul>
import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
heroes: Hero[];
constructor(private heroService: HeroService) { }
ngOnInit() {
this.getHeroes();
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
}
/* HeroesComponent's private CSS styles */
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
position: relative;
cursor: pointer;
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 a {
color: #333;
text-decoration: none;
position: relative;
display: block;
width: 250px;
}
.heroes a:hover {
color:#607D8B;
}
.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;
min-width: 16px;
text-align: right;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
<div *ngIf="hero">
<h2>{{hero.name | uppercase}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<div>
<label>name:
<input [(ngModel)]="hero.name" placeholder="name"/>
</label>
</div>
<button (click)="goBack()">go back</button>
</div>
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: [ './hero-detail.component.css' ]
})
export class HeroDetailComponent implements OnInit {
hero: Hero;
constructor(
private route: ActivatedRoute,
private heroService: HeroService,
private location: Location
) {}
ngOnInit(): void {
this.getHero();
}
getHero(): void {
const id = +this.route.snapshot.paramMap.get('id');
this.heroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
goBack(): void {
this.location.back();
}
}
/* DashboardComponent's private CSS styles */
[class*='col-'] {
float: left;
padding-right: 20px;
padding-bottom: 20px;
}
[class*='col-']:last-of-type {
padding-right: 0;
}
a {
text-decoration: none;
}
*, *:after, *:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
h3 {
text-align: center;
margin-bottom: 0;
}
h4 {
position: relative;
}
.grid {
margin: 0;
}
.col-1-4 {
width: 25%;
}
.module {
padding: 20px;
text-align: center;
color: #eee;
max-height: 120px;
min-width: 120px;
background-color: #3f525c;
border-radius: 2px;
}
.module:hover {
background-color: #eee;
cursor: pointer;
color: #607d8b;
}
.grid-pad {
padding: 10px 0;
}
.grid-pad > [class*='col-']:last-of-type {
padding-right: 20px;
}
@media (max-width: 600px) {
.module {
font-size: 10px;
max-height: 75px; }
}
@media (max-width: 1024px) {
.grid {
margin: 0;
}
.module {
min-width: 60px;
}
}
HeroesComponent
src/app/heroes/heroes.component.html
src/app/heroes/heroes.component.ts
src/app/heroes/heroes.component.css
content_copy
/* HeroesComponent's private CSS styles */
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
position: relative;
cursor: pointer;
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 a {
color: #333;
text-decoration: none;
position: relative;
display: block;
width: 250px;
}
.heroes a:hover {
color:#607D8B;
}
.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;
min-width: 16px;
text-align: right;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
<a>
鏈接和一個(gè) <router-outlet>
把 AppComponent 轉(zhuǎn)換成了一個(gè)導(dǎo)航用的殼組件。AppRoutingModule
中配置了路由器。routerLink
指令。
更多建議: