Hero guide 添加應(yīng)用內(nèi)導(dǎo)航

2020-07-01 10:55 更新

假想

假設(shè)有一些“英雄指南”的新需求:

  • 添加一個(gè)儀表盤(pán)視圖。

  • 添加在英雄列表和儀表盤(pán)視圖之間導(dǎo)航的能力。

  • 無(wú)論在哪個(gè)視圖中點(diǎn)擊一個(gè)英雄,都會(huì)導(dǎo)航到該英雄的詳情頁(yè)。

  • 在郵件中點(diǎn)擊一個(gè)深鏈接,會(huì)直接打開(kāi)一個(gè)特定英雄的詳情視圖。

完成效果:

添加 AppRoutingModule

在 Angular 中,最好在一個(gè)獨(dú)立的頂層模塊中加載和配置路由器,它專注于路由功能,然后由根模塊 AppModule 導(dǎo)入它。

按照慣例,這個(gè)模塊類的名字叫做 AppRoutingModule,并且位于 "src/app" 下的 "app-routing.module.ts" 文件中。

使用 CLI 生成它。

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

  1. import { NgModule } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. @NgModule({
  4. imports: [
  5. CommonModule
  6. ],
  7. declarations: []
  8. })
  9. export class AppRoutingModule { }

把它替換如下:

Path:"src/app/app-routing.module.ts (updated)"

  1. import { NgModule } from '@angular/core';
  2. import { RouterModule, Routes } from '@angular/router';
  3. import { HeroesComponent } from './heroes/heroes.component';
  4. const routes: Routes = [
  5. { path: 'heroes', component: HeroesComponent }
  6. ];
  7. @NgModule({
  8. imports: [RouterModule.forRoot(routes)],
  9. exports: [RouterModule]
  10. })
  11. 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)用戶單擊鏈接或?qū)?URL 粘貼進(jìn)瀏覽器地址欄時(shí)要顯示哪個(gè)視圖。

由于 AppRoutingModule 已經(jīng)導(dǎo)入了 HeroesComponent,因此你可以直接在 routes 數(shù)組中使用它:

Path:"src/app/app-routing.module.ts"

  1. const routes: Routes = [
  2. { path: 'heroes', component: HeroesComponent }
  3. ];

典型的 Angular Route 具有兩個(gè)屬性:

path: 用來(lái)匹配瀏覽器地址欄中 URL 的字符串。

component: 導(dǎo)航到該路由時(shí),路由器應(yīng)該創(chuàng)建的組件。

這會(huì)告訴路由器把該 URL 與 path:'heroes' 匹配。 如果網(wǎng)址類似于 "localhost:4200/heroes" 就顯示 HeroesComponent。

RouterModule.forRoot()

@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"

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

  1. exports: [ RouterModule ]

添加路由出口 RouterOutlet

打開(kāi) AppComponent 的模板,把 <app-heroes> 元素替換為 <router-outlet> 元素。

Path:"src/app/app.component.html (router-outlet)"

  1. <h1>{{title}}</h1>
  2. <router-outlet></router-outlet>
  3. <app-messages></app-messages>

AppComponent 的模板不再需要 <app-heroes>,因?yàn)橹挥挟?dāng)用戶導(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)的英雄顯示界面。

添加路由鏈接 (routerLink)

理想情況下,用戶應(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)"

  1. <h1>{{title}}</h1>
  2. <nav>
  3. <a routerLink="/heroes">Heroes</a>
  4. </nav>
  5. <router-outlet></router-outlet>
  6. <app-messages></app-messages>

routerLink 屬性的值為 "/heroes",路由器會(huì)用它來(lái)匹配出指向 HeroesComponent 的路由。 routerLinkRouterLink 指令的選擇器,它會(huì)把用戶的點(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)。

添加儀表盤(pán)視圖

當(dāng)有多個(gè)視圖時(shí),路由會(huì)更有價(jià)值。不過(guò)目前還只有一個(gè)英雄列表視圖。

使用 CLI 添加一個(gè) DashboardComponent:

  1. ng generate component dashboard

CLI 生成了 DashboardComponent 的相關(guān)文件,并把它聲明到 AppModule 中。

把這三個(gè)文件中的內(nèi)容改成這樣:

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

  1. <h3>Top Heroes</h3>
  2. <div class="grid grid-pad">
  3. <a *ngFor="let hero of heroes" class="col-1-4">
  4. <div class="module hero">
  5. <h4>{{hero.name}}</h4>
  6. </div>
  7. </a>
  8. </div>

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

  1. import { Component, OnInit } from '@angular/core';
  2. import { Hero } from '../hero';
  3. import { HeroService } from '../hero.service';
  4. @Component({
  5. selector: 'app-dashboard',
  6. templateUrl: './dashboard.component.html',
  7. styleUrls: [ './dashboard.component.css' ]
  8. })
  9. export class DashboardComponent implements OnInit {
  10. heroes: Hero[] = [];
  11. constructor(private heroService: HeroService) { }
  12. ngOnInit() {
  13. this.getHeroes();
  14. }
  15. getHeroes(): void {
  16. this.heroService.getHeroes()
  17. .subscribe(heroes => this.heroes = heroes.slice(1, 5));
  18. }
  19. }

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

  1. /* DashboardComponent's private CSS styles */
  2. [class*='col-'] {
  3. float: left;
  4. padding-right: 20px;
  5. padding-bottom: 20px;
  6. }
  7. [class*='col-']:last-of-type {
  8. padding-right: 0;
  9. }
  10. a {
  11. text-decoration: none;
  12. }
  13. *, *:after, *:before {
  14. -webkit-box-sizing: border-box;
  15. -moz-box-sizing: border-box;
  16. box-sizing: border-box;
  17. }
  18. h3 {
  19. text-align: center;
  20. margin-bottom: 0;
  21. }
  22. h4 {
  23. position: relative;
  24. }
  25. .grid {
  26. margin: 0;
  27. }
  28. .col-1-4 {
  29. width: 25%;
  30. }
  31. .module {
  32. padding: 20px;
  33. text-align: center;
  34. color: #eee;
  35. max-height: 120px;
  36. min-width: 120px;
  37. background-color: #3f525c;
  38. border-radius: 2px;
  39. }
  40. .module:hover {
  41. background-color: #eee;
  42. cursor: pointer;
  43. color: #607d8b;
  44. }
  45. .grid-pad {
  46. padding: 10px 0;
  47. }
  48. .grid-pad > [class*='col-']:last-of-type {
  49. padding-right: 20px;
  50. }
  51. @media (max-width: 600px) {
  52. .module {
  53. font-size: 10px;
  54. max-height: 75px; }
  55. }
  56. @media (max-width: 1024px) {
  57. .grid {
  58. margin: 0;
  59. }
  60. .module {
  61. min-width: 60px;
  62. }
  63. }

這個(gè)模板用來(lái)表示由英雄名字鏈接組成的一個(gè)陣列。

*ngFor 復(fù)寫(xiě)器為組件的 heroes 數(shù)組中的每個(gè)條目創(chuàng)建了一個(gè)鏈接。

這些鏈接被 dashboard.component.css 中的樣式格式化成了一些色塊。

這些鏈接還沒(méi)有指向任何地方,但很快就會(huì)了。

這個(gè)類和 HeroesComponent 類很像。

它定義了一個(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"

  1. getHeroes(): void {
  2. this.heroService.getHeroes()
  3. .subscribe(heroes => this.heroes = heroes.slice(1, 5));
  4. }

添加儀表盤(pán)路由

要導(dǎo)航到儀表盤(pán),路由器中就需要一個(gè)相應(yīng)的路由。

把 DashboardComponent 導(dǎo)入到 AppRoutingModule 中。

Path:"src/app/app-routing.module.ts (import DashboardComponent)"

  1. import { DashboardComponent } from './dashboard/dashboard.component';

把一個(gè)指向 DashboardComponent 的路由添加到 AppRoutingModule.routes 數(shù)組中。

Path:"src/app/app-routing.module.ts"

  1. { path: 'dashboard', component: DashboardComponent },

添加默認(rèn)路由

當(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"

  1. { path: '', redirectTo: '/dashboard', pathMatch: 'full' },

這個(gè)路由會(huì)把一個(gè)與空路徑“完全匹配”的 URL 重定向到路徑為 '/dashboard' 的路由。

瀏覽器刷新之后,路由器加載了 DashboardComponent,并且瀏覽器的地址欄會(huì)顯示出 /dashboard 這個(gè) URL。

把儀表盤(pán)鏈接添加到殼組件中

應(yīng)該允許用戶通過(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"

  1. <h1>{{title}}</h1>
  2. <nav>
  3. <a routerLink="/dashboard">Dashboard</a>
  4. <a routerLink="/heroes">Heroes</a>
  5. </nav>
  6. <router-outlet></router-outlet>
  7. <app-messages></app-messages>

刷新瀏覽器,你就能通過(guò)點(diǎn)擊這些鏈接在這兩個(gè)視圖之間自由導(dǎo)航了。

導(dǎo)航到英雄詳情

HeroDetailComponent 可以顯示所選英雄的詳情。 此刻,HeroDetailsComponent 只能在 HeroesComponent 的底部看到。

用戶應(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)。

從 HeroesComponent 中刪除英雄詳情

當(dāng)用戶在 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 的英雄的詳情視圖,類似于 ~/detail/11 的 URL 將是一個(gè)不錯(cuò)的 URL。

打開(kāi) AppRoutingModule 并導(dǎo)入 HeroDetailComponent

Path:"src/app/app-routing.module.ts (import HeroDetailComponent)"

  1. import { HeroDetailComponent } from './hero-detail/hero-detail.component';

然后把一個(gè)參數(shù)化路由添加到 AppRoutingModule.routes 數(shù)組中,它要匹配指向英雄詳情視圖的路徑。

Path:"src/app/app-routing.module.ts"

  1. { path: 'detail/:id', component: HeroDetailComponent },

path 中的冒號(hào)(:)表示 :id 是一個(gè)占位符,它表示某個(gè)特定英雄的 id。

此刻,應(yīng)用中的所有路由都就緒了。

Path:"src/app/app-routing.module.ts (all routes)"

  1. const routes: Routes = [
  2. { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  3. { path: 'dashboard', component: DashboardComponent },
  4. { path: 'detail/:id', component: HeroDetailComponent },
  5. { path: 'heroes', component: HeroesComponent }
  6. ];

DashboardComponent 中的英雄鏈接

此刻,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))"

  1. <a *ngFor="let hero of heroes" class="col-1-4"
  2. routerLink="/detail/{{hero.id}}">
  3. <div class="module hero">
  4. <h4>{{hero.name}}</h4>
  5. </div>
  6. </a>

你正在 *ngFor 復(fù)寫(xiě)器中使用 Angular 的插值綁定來(lái)把當(dāng)前迭代的 hero.id 插入到每個(gè) routerLink 中。

HeroesComponent 中的英雄鏈接

HeroesComponent 中的這些英雄條目都是 <li> 元素,它們的點(diǎn)擊事件都綁定到了組件的 onSelect() 方法中。

Path:"src/app/heroes/heroes.component.html (list with onSelect)"

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

清理 <li>,只保留它的 *ngFor,把徽章(<badge>)和名字包裹進(jìn)一個(gè) <a> 元素中, 并且像儀表盤(pán)的模板中那樣為這個(gè) <a> 元素添加一個(gè) routerLink 屬性。

Path:"src/app/heroes/heroes.component.html (list with links)"

  1. <ul class="heroes">
  2. <li *ngFor="let hero of heroes">
  3. <a routerLink="/detail/{{hero.id}}">
  4. <span class="badge">{{hero.id}}</span> {{hero.name}}
  5. </a>
  6. </li>
  7. </ul>

你還要修改私有樣式表(heroes.component.css),讓列表恢復(fù)到以前的外觀。 修改后的樣式表參見(jiàn)本指南底部的最終代碼。

移除死代碼(可選)

雖然 HeroesComponent 類仍然能正常工作,但 onSelect() 方法和 selectedHero 屬性已經(jīng)沒(méi)用了。

最好清理掉它們,將來(lái)你會(huì)體會(huì)到這么做的好處。 下面是刪除了死代碼之后的類。

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

  1. export class HeroesComponent implements OnInit {
  2. heroes: Hero[];
  3. constructor(private heroService: HeroService) { }
  4. ngOnInit() {
  5. this.getHeroes();
  6. }
  7. getHeroes(): void {
  8. this.heroService.getHeroes()
  9. .subscribe(heroes => this.heroes = heroes);
  10. }
  11. }

支持路由的 HeroDetailComponent

以前,父組件 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ì)講解如下操作:

  • 獲取創(chuàng)建本組件的路由。

  • 從這個(gè)路由中提取出 id

  • 通過(guò) HeroService 從服務(wù)器上獲取具有這個(gè) id 的英雄數(shù)據(jù)。

先添加下列導(dǎo)入語(yǔ)句:

Path:"src/app/hero-detail/hero-detail.component.ts"

  1. import { ActivatedRoute } from '@angular/router';
  2. import { Location } from '@angular/common';
  3. import { HeroService } from '../hero.service';

然后把 ActivatedRoute、HeroService 和 Location 服務(wù)注入到構(gòu)造函數(shù)中,將它們的值保存到私有變量里:

Path:"src/app/hero-detail/hero-detail.component.ts"

  1. constructor(
  2. private route: ActivatedRoute,
  3. private heroService: HeroService,
  4. private location: Location
  5. ) {}

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è)視圖。

從路由參數(shù)中提取 id

ngOnInit() 生命周期鉤子 中調(diào)用 getHero(),代碼如下:

Path:"src/app/hero-detail/hero-detail.component.ts"

  1. ngOnInit(): void {
  2. this.getHero();
  3. }
  4. getHero(): void {
  5. const id = +this.route.snapshot.paramMap.get('id');
  6. this.heroService.getHero(id)
  7. .subscribe(hero => this.hero = hero);
  8. }

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ù)字類型。

刷新瀏覽器,應(yīng)用掛了。出現(xiàn)一個(gè)編譯錯(cuò)誤,因?yàn)?HeroService 沒(méi)有一個(gè)名叫 getHero() 的方法。 這就添加它。

添加 HeroService.getHero()

添加 HeroService,并在 getHeroes() 后面添加如下的 getHero() 方法,它接收 id 參數(shù):

Path:"src/app/hero.service.ts (getHero) "

  1. getHero(id: number): Observable<Hero> {
  2. // TODO: send the message _after_ fetching the hero
  3. this.messageService.add(`HeroService: fetched hero id=${id}`);
  4. return of(HEROES.find(hero => hero.id === id));
  5. }

注:
- 反引號(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)"

  1. <button (click)="goBack()">go back</button>

在組件類中添加一個(gè) goBack() 方法,利用你以前注入的 Location 服務(wù)在瀏覽器的歷史棧中后退一步。

Path:"src/app/hero-detail/hero-detail.component.ts (goBack)"

  1. goBack(): void {
  2. this.location.back();
  3. }

刷新瀏覽器,并開(kāi)始點(diǎn)擊。 用戶能在應(yīng)用中導(dǎo)航:從儀表盤(pán)到英雄詳情再回來(lái),從英雄列表到 mini 版英雄詳情到英雄詳情,再回到英雄列表。

查看最終代碼

AppRoutingModule

Path:"src/app/app-routing.module.ts"

  1. import { NgModule } from '@angular/core';
  2. import { RouterModule, Routes } from '@angular/router';
  3. import { DashboardComponent } from './dashboard/dashboard.component';
  4. import { HeroesComponent } from './heroes/heroes.component';
  5. import { HeroDetailComponent } from './hero-detail/hero-detail.component';
  6. const routes: Routes = [
  7. { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  8. { path: 'dashboard', component: DashboardComponent },
  9. { path: 'detail/:id', component: HeroDetailComponent },
  10. { path: 'heroes', component: HeroesComponent }
  11. ];
  12. @NgModule({
  13. imports: [ RouterModule.forRoot(routes) ],
  14. exports: [ RouterModule ]
  15. })
  16. export class AppRoutingModule {}

AppModule

Path:"src/app/app.module.ts"

  1. import { NgModule } from '@angular/core';
  2. import { BrowserModule } from '@angular/platform-browser';
  3. import { FormsModule } from '@angular/forms';
  4. import { AppComponent } from './app.component';
  5. import { DashboardComponent } from './dashboard/dashboard.component';
  6. import { HeroDetailComponent } from './hero-detail/hero-detail.component';
  7. import { HeroesComponent } from './heroes/heroes.component';
  8. import { MessagesComponent } from './messages/messages.component';
  9. import { AppRoutingModule } from './app-routing.module';
  10. @NgModule({
  11. imports: [
  12. BrowserModule,
  13. FormsModule,
  14. AppRoutingModule
  15. ],
  16. declarations: [
  17. AppComponent,
  18. DashboardComponent,
  19. HeroesComponent,
  20. HeroDetailComponent,
  21. MessagesComponent
  22. ],
  23. bootstrap: [ AppComponent ]
  24. })
  25. export class AppModule { }

HeroService

Path:"src/app/hero.service.ts"

  1. import { Injectable } from '@angular/core';
  2. import { Observable, of } from 'rxjs';
  3. import { Hero } from './hero';
  4. import { HEROES } from './mock-heroes';
  5. import { MessageService } from './message.service';
  6. @Injectable({ providedIn: 'root' })
  7. export class HeroService {
  8. constructor(private messageService: MessageService) { }
  9. getHeroes(): Observable<Hero[]> {
  10. // TODO: send the message _after_ fetching the heroes
  11. this.messageService.add('HeroService: fetched heroes');
  12. return of(HEROES);
  13. }
  14. getHero(id: number): Observable<Hero> {
  15. // TODO: send the message _after_ fetching the hero
  16. this.messageService.add(`HeroService: fetched hero id=${id}`);
  17. return of(HEROES.find(hero => hero.id === id));
  18. }
  19. }

AppComponent

  • Path:"src/app/app.component.html"
    1. <h1>{{title}}</h1>
    2. <nav>
    3. <a routerLink="/dashboard">Dashboard</a>
    4. <a routerLink="/heroes">Heroes</a>
    5. </nav>
    6. <router-outlet></router-outlet>
    7. <app-messages></app-messages>

  • Path:"src/app/app.component.css"
    1. /* AppComponent's private CSS styles */
    2. h1 {
    3. font-size: 1.2em;
    4. margin-bottom: 0;
    5. }
    6. h2 {
    7. font-size: 2em;
    8. margin-top: 0;
    9. padding-top: 0;
    10. }
    11. nav a {
    12. padding: 5px 10px;
    13. text-decoration: none;
    14. margin-top: 10px;
    15. display: inline-block;
    16. background-color: #eee;
    17. border-radius: 4px;
    18. }
    19. nav a:visited, a:link {
    20. color: #334953;
    21. }
    22. nav a:hover {
    23. color: #039be5;
    24. background-color: #cfd8dc;
    25. }
    26. nav a.active {
    27. color: #039be5;
    28. }

DashboardComponent

  • Path:"src/app/dashboard/dashboard.component.html"
    1. <h3>Top Heroes</h3>
    2. <div class="grid grid-pad">
    3. <a *ngFor="let hero of heroes" class="col-1-4"
    4. routerLink="/detail/{{hero.id}}">
    5. <div class="module hero">
    6. <h4>{{hero.name}}</h4>
    7. </div>
    8. </a>
    9. </div>

  • Path:"src/app/dashboard/dashboard.component.css"

  1. import { Component, OnInit } from '@angular/core';
  2. import { Hero } from '../hero';
  3. import { HeroService } from '../hero.service';
  4. @Component({
  5. selector: 'app-dashboard',
  6. templateUrl: './dashboard.component.html',
  7. styleUrls: [ './dashboard.component.css' ]
  8. })
  9. export class DashboardComponent implements OnInit {
  10. heroes: Hero[] = [];
  11. constructor(private heroService: HeroService) { }
  12. ngOnInit() {
  13. this.getHeroes();
  14. }
  15. getHeroes(): void {
  16. this.heroService.getHeroes()
  17. .subscribe(heroes => this.heroes = heroes.slice(1, 5));
  18. }
  19. }

  • Path:"src/app/dashboard/dashboard.component.css"

  1. /* DashboardComponent's private CSS styles */
  2. [class*='col-'] {
  3. float: left;
  4. padding-right: 20px;
  5. padding-bottom: 20px;
  6. }
  7. [class*='col-']:last-of-type {
  8. padding-right: 0;
  9. }
  10. a {
  11. text-decoration: none;
  12. }
  13. *, *:after, *:before {
  14. -webkit-box-sizing: border-box;
  15. -moz-box-sizing: border-box;
  16. box-sizing: border-box;
  17. }
  18. h3 {
  19. text-align: center;
  20. margin-bottom: 0;
  21. }
  22. h4 {
  23. position: relative;
  24. }
  25. .grid {
  26. margin: 0;
  27. }
  28. .col-1-4 {
  29. width: 25%;
  30. }
  31. .module {
  32. padding: 20px;
  33. text-align: center;
  34. color: #eee;
  35. max-height: 120px;
  36. min-width: 120px;
  37. background-color: #3f525c;
  38. border-radius: 2px;
  39. }
  40. .module:hover {
  41. background-color: #eee;
  42. cursor: pointer;
  43. color: #607d8b;
  44. }
  45. .grid-pad {
  46. padding: 10px 0;
  47. }
  48. .grid-pad > [class*='col-']:last-of-type {
  49. padding-right: 20px;
  50. }
  51. @media (max-width: 600px) {
  52. .module {
  53. font-size: 10px;
  54. max-height: 75px; }
  55. }
  56. @media (max-width: 1024px) {
  57. .grid {
  58. margin: 0;
  59. }
  60. .module {
  61. min-width: 60px;
  62. }
  63. }

HeroesComponent

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

  1. <h2>My Heroes</h2>
  2. <ul class="heroes">
  3. <li *ngFor="let hero of heroes">
  4. <a routerLink="/detail/{{hero.id}}">
  5. <span class="badge">{{hero.id}}</span> {{hero.name}}
  6. </a>
  7. </li>
  8. </ul>

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

  1. import { Component, OnInit } from '@angular/core';
  2. import { Hero } from '../hero';
  3. import { HeroService } from '../hero.service';
  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: Hero[];
  11. constructor(private heroService: HeroService) { }
  12. ngOnInit() {
  13. this.getHeroes();
  14. }
  15. getHeroes(): void {
  16. this.heroService.getHeroes()
  17. .subscribe(heroes => this.heroes = heroes);
  18. }
  19. }

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

  1. /* DashboardComponent's private CSS styles */
  2. [class*='col-'] {
  3. float: left;
  4. padding-right: 20px;
  5. padding-bottom: 20px;
  6. }
  7. [class*='col-']:last-of-type {
  8. padding-right: 0;
  9. }
  10. a {
  11. text-decoration: none;
  12. }
  13. *, *:after, *:before {
  14. -webkit-box-sizing: border-box;
  15. -moz-box-sizing: border-box;
  16. box-sizing: border-box;
  17. }
  18. h3 {
  19. text-align: center;
  20. margin-bottom: 0;
  21. }
  22. h4 {
  23. position: relative;
  24. }
  25. .grid {
  26. margin: 0;
  27. }
  28. .col-1-4 {
  29. width: 25%;
  30. }
  31. .module {
  32. padding: 20px;
  33. text-align: center;
  34. color: #eee;
  35. max-height: 120px;
  36. min-width: 120px;
  37. background-color: #3f525c;
  38. border-radius: 2px;
  39. }
  40. .module:hover {
  41. background-color: #eee;
  42. cursor: pointer;
  43. color: #607d8b;
  44. }
  45. .grid-pad {
  46. padding: 10px 0;
  47. }
  48. .grid-pad > [class*='col-']:last-of-type {
  49. padding-right: 20px;
  50. }
  51. @media (max-width: 600px) {
  52. .module {
  53. font-size: 10px;
  54. max-height: 75px; }
  55. }
  56. @media (max-width: 1024px) {
  57. .grid {
  58. margin: 0;
  59. }
  60. .module {
  61. min-width: 60px;
  62. }
  63. }

HeroesComponent

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

  1. <h2>My Heroes</h2>
  2. <ul class="heroes">
  3. <li *ngFor="let hero of heroes">
  4. <a routerLink="/detail/{{hero.id}}">
  5. <span class="badge">{{hero.id}}</span> {{hero.name}}
  6. </a>
  7. </li>
  8. </ul>

  • Path:"ssrc/app/heroes/heroes.component.ts"

  1. import { Component, OnInit } from '@angular/core';
  2. import { Hero } from '../hero';
  3. import { HeroService } from '../hero.service';
  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: Hero[];
  11. constructor(private heroService: HeroService) { }
  12. ngOnInit() {
  13. this.getHeroes();
  14. }
  15. getHeroes(): void {
  16. this.heroService.getHeroes()
  17. .subscribe(heroes => this.heroes = heroes);
  18. }
  19. }

  • 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. position: relative;
  10. cursor: pointer;
  11. background-color: #EEE;
  12. margin: .5em;
  13. padding: .3em 0;
  14. height: 1.6em;
  15. border-radius: 4px;
  16. }
  17. .heroes li:hover {
  18. color: #607D8B;
  19. background-color: #DDD;
  20. left: .1em;
  21. }
  22. .heroes a {
  23. color: #333;
  24. text-decoration: none;
  25. position: relative;
  26. display: block;
  27. width: 250px;
  28. }
  29. .heroes a:hover {
  30. color:#607D8B;
  31. }
  32. .heroes .badge {
  33. display: inline-block;
  34. font-size: small;
  35. color: white;
  36. padding: 0.8em 0.7em 0 0.7em;
  37. background-color:#405061;
  38. line-height: 1em;
  39. position: relative;
  40. left: -1px;
  41. top: -4px;
  42. height: 1.8em;
  43. min-width: 16px;
  44. text-align: right;
  45. margin-right: .8em;
  46. border-radius: 4px 0 0 4px;
  47. }

HeroDetailComponent

  • Path:"src/app/hero-detail/hero-detail.component.html"

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

  • Path:"src/app/hero-detail/hero-detail.component.ts"

  1. import { Component, OnInit } from '@angular/core';
  2. import { ActivatedRoute } from '@angular/router';
  3. import { Location } from '@angular/common';
  4. import { Hero } from '../hero';
  5. import { HeroService } from '../hero.service';
  6. @Component({
  7. selector: 'app-hero-detail',
  8. templateUrl: './hero-detail.component.html',
  9. styleUrls: [ './hero-detail.component.css' ]
  10. })
  11. export class HeroDetailComponent implements OnInit {
  12. hero: Hero;
  13. constructor(
  14. private route: ActivatedRoute,
  15. private heroService: HeroService,
  16. private location: Location
  17. ) {}
  18. ngOnInit(): void {
  19. this.getHero();
  20. }
  21. getHero(): void {
  22. const id = +this.route.snapshot.paramMap.get('id');
  23. this.heroService.getHero(id)
  24. .subscribe(hero => this.hero = hero);
  25. }
  26. goBack(): void {
  27. this.location.back();
  28. }
  29. }

  • Path:"src/app/hero-detail/hero-detail.component.css"

  1. /* DashboardComponent's private CSS styles */
  2. [class*='col-'] {
  3. float: left;
  4. padding-right: 20px;
  5. padding-bottom: 20px;
  6. }
  7. [class*='col-']:last-of-type {
  8. padding-right: 0;
  9. }
  10. a {
  11. text-decoration: none;
  12. }
  13. *, *:after, *:before {
  14. -webkit-box-sizing: border-box;
  15. -moz-box-sizing: border-box;
  16. box-sizing: border-box;
  17. }
  18. h3 {
  19. text-align: center;
  20. margin-bottom: 0;
  21. }
  22. h4 {
  23. position: relative;
  24. }
  25. .grid {
  26. margin: 0;
  27. }
  28. .col-1-4 {
  29. width: 25%;
  30. }
  31. .module {
  32. padding: 20px;
  33. text-align: center;
  34. color: #eee;
  35. max-height: 120px;
  36. min-width: 120px;
  37. background-color: #3f525c;
  38. border-radius: 2px;
  39. }
  40. .module:hover {
  41. background-color: #eee;
  42. cursor: pointer;
  43. color: #607d8b;
  44. }
  45. .grid-pad {
  46. padding: 10px 0;
  47. }
  48. .grid-pad > [class*='col-']:last-of-type {
  49. padding-right: 20px;
  50. }
  51. @media (max-width: 600px) {
  52. .module {
  53. font-size: 10px;
  54. max-height: 75px; }
  55. }
  56. @media (max-width: 1024px) {
  57. .grid {
  58. margin: 0;
  59. }
  60. .module {
  61. min-width: 60px;
  62. }
  63. }
  64. HeroesComponent
  65. src/app/heroes/heroes.component.html
  66. src/app/heroes/heroes.component.ts
  67. src/app/heroes/heroes.component.css
  68. content_copy
  69. /* HeroesComponent's private CSS styles */
  70. .heroes {
  71. margin: 0 0 2em 0;
  72. list-style-type: none;
  73. padding: 0;
  74. width: 15em;
  75. }
  76. .heroes li {
  77. position: relative;
  78. cursor: pointer;
  79. background-color: #EEE;
  80. margin: .5em;
  81. padding: .3em 0;
  82. height: 1.6em;
  83. border-radius: 4px;
  84. }
  85. .heroes li:hover {
  86. color: #607D8B;
  87. background-color: #DDD;
  88. left: .1em;
  89. }
  90. .heroes a {
  91. color: #333;
  92. text-decoration: none;
  93. position: relative;
  94. display: block;
  95. width: 250px;
  96. }
  97. .heroes a:hover {
  98. color:#607D8B;
  99. }
  100. .heroes .badge {
  101. display: inline-block;
  102. font-size: small;
  103. color: white;
  104. padding: 0.8em 0.7em 0 0.7em;
  105. background-color:#405061;
  106. line-height: 1em;
  107. position: relative;
  108. left: -1px;
  109. top: -4px;
  110. height: 1.8em;
  111. min-width: 16px;
  112. text-align: right;
  113. margin-right: .8em;
  114. border-radius: 4px 0 0 4px;
  115. }

總結(jié)

  • 添加了 Angular 路由器在各個(gè)不同組件之間導(dǎo)航。

  • 您使用一些 <a> 鏈接和一個(gè) <router-outlet> 把 AppComponent 轉(zhuǎn)換成了一個(gè)導(dǎo)航用的殼組件。

  • 您在 AppRoutingModule 中配置了路由器。

  • 您定義了一些簡(jiǎn)單路由、一個(gè)重定向路由和一個(gè)參數(shù)化路由。

  • 您在 <a> 元素中使用了 routerLink 指令。

  • 您把一個(gè)緊耦合的主從視圖重構(gòu)成了帶路由的詳情視圖。

  • 您使用路由鏈接參數(shù)來(lái)導(dǎo)航到所選英雄的詳情視圖。

  • 在多個(gè)組件之間共享了 HeroService 服務(wù)。
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)