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ú)立的頂層模塊中加載和配置路由器,它專(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。

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"

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 ]

添加路由出口 RouterOutlet

打開(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)的英雄顯示界面。

添加路由鏈接 (routerLink)

理想情況下,用戶(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 的路由。 routerLinkRouterLink 指令的選擇器,它會(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)。

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

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

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

ng generate component dashboard

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

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

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

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

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

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));
  }
}

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

/* 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));
}

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

要導(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 },

添加默認(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"

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

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

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

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

應(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)航了。

導(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)。

從 HeroesComponent 中刪除英雄詳情

當(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 中的英雄鏈接

此刻,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 中的英雄鏈接

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);
  }
}

支持路由的 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"

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

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

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.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 版英雄詳情到英雄詳情,再回到英雄列表。

查看最終代碼

AppRoutingModule

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 {}

AppModule

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 { }

HeroService

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));
      }
    }

AppComponent

  • 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>

  • Path:"src/app/app.component.css"
    /* 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;
    }

DashboardComponent

  • Path:"src/app/dashboard/dashboard.component.html"
    <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>

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

    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));
      }
    }

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

    /* 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

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

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

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

    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);
      }
    }

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

    /* 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

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

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

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

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);
  }
}

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

/* 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;
}

HeroDetailComponent

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

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

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

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();
  }
}

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

/* 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;
}

總結(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ì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)