Angular 英雄之旅-添加導(dǎo)航

2022-07-19 11:43 更新

用路由添加導(dǎo)航支持

有一些《英雄之旅》的新需求:

  • 添加一個(gè)儀表盤(pán)視圖
  • 添加在英雄列表儀表盤(pán)視圖之間導(dǎo)航的能力
  • 無(wú)論在哪個(gè)視圖中點(diǎn)擊一個(gè)英雄,都會(huì)導(dǎo)航到該英雄的詳情頁(yè)
  • 在郵件中點(diǎn)擊一個(gè)深鏈接,會(huì)直接打開(kāi)一個(gè)特定英雄的詳情視圖

要查看本頁(yè)所講的范例程序,參閱現(xiàn)場(chǎng)演練 / 下載范例。

完成時(shí),用戶就能像這樣在應(yīng)用中導(dǎo)航:


添加 AppRoutingModule

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

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

使用 CLI 生成它。

ng generate module app-routing --flat --module=app

參數(shù)

詳情

--flat

把這個(gè)文件放進(jìn)了 src/app 中,而不是單獨(dú)的目錄中。

--module=app

告訴 CLI 把它注冊(cè)到 AppModule 的 imports 數(shù)組中。

生成的文件是這樣的:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

@NgModule({
  imports: [
    CommonModule
  ],
  declarations: []
})
export class AppRoutingModule { }

把它替換為如下代碼:

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

首先,?app-routing.module.ts? 會(huì)導(dǎo)入 ?RouterModule ?和 ?Routes?,以便該應(yīng)用具有路由功能。配置好路由后,接著導(dǎo)入 ?HeroesComponent?,它將告訴路由器要去什么地方。

注意,對(duì) ?CommonModule ?的引用和 ?declarations ?數(shù)組不是必要的,因此它們不再是 ?AppRoutingModule ?的一部分。以下各節(jié)將詳細(xì)介紹 ?AppRoutingModule ?的其余部分。

路由

該文件的下一部分是你的路由配置。Routes 告訴路由器,當(dāng)用戶單擊鏈接或?qū)?nbsp;URL 粘貼進(jìn)瀏覽器地址欄時(shí)要顯示哪個(gè)視圖。

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

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)址類似于 ?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 ?配置它:

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)用程序中生效。

exports: [ RouterModule ]

添加 RouterOutlet

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

<h1>{{title}}</h1>
<router-outlet></router-outlet>
<app-messages></app-messages>

?AppComponent ?的模板不再需要 ?<app-heroes>?,因?yàn)橹挥挟?dāng)用戶導(dǎo)航到這里時(shí),才需要顯示 ?HeroesComponent?。

?<router-outlet>? 會(huì)告訴路由器要在哪里顯示路由的視圖。

能在 ?AppComponent ?中使用 ?RouterOutlet?,是因?yàn)?nbsp;?AppModule ?導(dǎo)入了 ?AppRoutingModule?,而 ?AppRoutingModule ?中導(dǎo)出了 ?RouterModule?。在本教程開(kāi)始時(shí)你運(yùn)行的那個(gè) ?ng generate? 命令添加了這個(gè)導(dǎo)入,是因?yàn)?nbsp;?--module=app? 標(biāo)志。如果你手動(dòng)創(chuàng)建 ?app-routing.module.ts? 或使用了 CLI 之外的工具,你就要把 ?AppRoutingModule ?導(dǎo)入到 ?app.module.ts? 中,并且把它添加到 ?NgModule ?的 ?imports ?數(shù)組中

試試看

你的 CLI 命令應(yīng)該仍在運(yùn)行吧。

ng serve

瀏覽器應(yīng)該刷新,并顯示著應(yīng)用的標(biāo)題,但是沒(méi)有顯示英雄列表。

看看瀏覽器的地址欄。URL 是以 ?/? 結(jié)尾的。而到 ?HeroesComponent ?的路由路徑是 ?/heroes?。

在地址欄中把 ?/heroes? 追加到 URL 后面。你應(yīng)該能看到熟悉的主從結(jié)構(gòu)的英雄顯示界面。

從瀏覽器地址欄中的 URL 中移除 ?/heroes?。瀏覽器就會(huì)刷新,并且顯示本應(yīng)用的標(biāo)題,而不顯示英雄列表。

添加路由鏈接 (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 ?模板如下:

<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ì)把用戶的點(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)容改成這樣:

  • src/app/dashboard/dashboard.component.html
  • <h2>Top Heroes</h2>
    <div class="heroes-menu">
      <a *ngFor="let hero of heroes">
        {{hero.name}}
      </a>
    </div>
  • 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(): void {
        this.getHeroes();
      }
    
      getHeroes(): void {
        this.heroService.getHeroes()
          .subscribe(heroes => this.heroes = heroes.slice(1, 5));
      }
    }
  • src/app/dashboard/dashboard.component.css
  • /* DashboardComponent's private CSS styles */
    
    h2 {
      text-align: center;
    }
    
    .heroes-menu {
      padding: 0;
      margin: auto;
      max-width: 1000px;
    
      /* flexbox */
      display: flex;
      flex-direction: row;
      flex-wrap: wrap;
      justify-content: space-around;
      align-content: flex-start;
      align-items: flex-start;
    }
    
    a {
      background-color: #3f525c;
      border-radius: 2px;
      padding: 1rem;
      font-size: 1.2rem;
      text-decoration: none;
      display: inline-block;
      color: #fff;
      text-align: center;
      width: 100%;
      min-width: 70px;
      margin: .5rem auto;
      box-sizing: border-box;
    
      /* flexbox */
      order: 0;
      flex: 0 1 auto;
      align-self: auto;
    }
    
    @media (min-width: 600px) {
      a {
        width: 18%;
        box-sizing: content-box;
      }
    }
    
    a:hover {
      background-color: #000;
    }

這個(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è)頂層英雄(第二,第三,第四和第五)。

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)入到 ?app-routing-module.ts? 中。

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

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

{ 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)把下列路由添加到 ?routes ?數(shù)組中。

{ 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 鏈接的前面。

<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 ?可以顯示所選英雄的詳情。此刻,?HeroDetailComponent ?只能在 ?HeroesComponent ?的底部看到。

用戶應(yīng)該能通過(guò)三種途徑看到這些詳情。

  1. 通過(guò)在儀表盤(pán)中點(diǎn)擊某個(gè)英雄。
  2. 通過(guò)在英雄列表中點(diǎn)擊某個(gè)英雄。
  3. 通過(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) ?app-routing.module.ts? 并導(dǎo)入 ?HeroDetailComponent?。

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

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

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

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

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

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)航。

<a *ngFor="let hero of heroes"
  routerLink="/detail/{{hero.id}}">
  {{hero.name}}
</a>

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

HeroesComponent 中的英雄鏈接

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

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

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

<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ù)到以前的外觀。

移除死代碼(可選)

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

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

export class HeroesComponent implements OnInit {
  heroes: Hero[] = [];

  constructor(private heroService: HeroService) { }

  ngOnInit(): void {
    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ì)再那么做了?,F(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ǔ)句:

import { ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common';

import { HeroService } from '../hero.service';

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

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()?,代碼如下。

ngOnInit(): void {
  this.getHero();
}

getHero(): void {
  const id = Number(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 的 ?Number ?函數(shù)會(huì)把字符串轉(zhuǎn)換成數(shù)字,英雄的 ?id ?就是數(shù)字類型。

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

添加 HeroService.getHero()

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

getHero(id: number): Observable<Hero> {
  // For now, assume that a hero with the specified `id` always exists.
  // Error handling will be added in the next step of the tutorial.
  const hero = HEROES.find(h => h.id === id)!;
  this.messageService.add(`HeroService: fetched hero id=${id}`);
  return of(hero);
}
重要:
反引號(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?。

試試看

刷新瀏覽器,應(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()? 方法。

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

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

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

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

當(dāng)你將一些私有 CSS 樣式添加到 ?hero-detail.component.css? 里之后,其細(xì)節(jié)看起來(lái)會(huì)更好,如下面的“查看最終代碼”標(biāo)簽頁(yè)中所示。

查看最終代碼

下面是本頁(yè)所提到的源代碼。

AppRoutingModule、AppModule 和 HeroService

  • 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 { }
  • 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 {}
  • 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[]> {
        const heroes = of(HEROES);
        this.messageService.add('HeroService: fetched heroes');
        return heroes;
      }
    
      getHero(id: number): Observable<Hero> {
        // For now, assume that a hero with the specified `id` always exists.
        // Error handling will be added in the next step of the tutorial.
        const hero = HEROES.find(h => h.id === id)!;
        this.messageService.add(`HeroService: fetched hero id=${id}`);
        return of(hero);
      }
    }

AppComponent

  • 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>
  • src/app/app.component.ts
  • import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent {
      title = 'Tour of Heroes';
    }
  • src/app/app.component.css
  • /* AppComponent's private CSS styles */
    h1 {
      margin-bottom: 0;
    }
    nav a {
      padding: 1rem;
      text-decoration: none;
      margin-top: 10px;
      display: inline-block;
      background-color: #e8e8e8;
      color: #3d3d3d;
      border-radius: 4px;
    }
    
    nav a:hover {
      color: white;
      background-color: #42545C;
    }
    nav a.active {
      background-color: black;
    }

DashboardComponent

  • src/app/dashboard/dashboard.component.html
  • <h2>Top Heroes</h2>
    <div class="heroes-menu">
      <a *ngFor="let hero of heroes"
        routerLink="/detail/{{hero.id}}">
        {{hero.name}}
      </a>
    </div>
  • 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(): void {
        this.getHeroes();
      }
    
      getHeroes(): void {
        this.heroService.getHeroes()
          .subscribe(heroes => this.heroes = heroes.slice(1, 5));
      }
    }
  • src/app/dashboard/dashboard.component.css
  • /* DashboardComponent's private CSS styles */
    
    h2 {
      text-align: center;
    }
    
    .heroes-menu {
      padding: 0;
      margin: auto;
      max-width: 1000px;
    
      /* flexbox */
      display: flex;
      flex-direction: row;
      flex-wrap: wrap;
      justify-content: space-around;
      align-content: flex-start;
      align-items: flex-start;
    }
    
    a {
      background-color: #3f525c;
      border-radius: 2px;
      padding: 1rem;
      font-size: 1.2rem;
      text-decoration: none;
      display: inline-block;
      color: #fff;
      text-align: center;
      width: 100%;
      min-width: 70px;
      margin: .5rem auto;
      box-sizing: border-box;
    
      /* flexbox */
      order: 0;
      flex: 0 1 auto;
      align-self: auto;
    }
    
    @media (min-width: 600px) {
      a {
        width: 18%;
        box-sizing: content-box;
      }
    }
    
    a:hover {
      background-color: #000;
    }

HeroesComponent

  • 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>
  • 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(): void {
        this.getHeroes();
      }
    
      getHeroes(): void {
        this.heroService.getHeroes()
        .subscribe(heroes => this.heroes = heroes);
      }
    }
  • 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;
    }
    
    .heroes li:hover {
      left: .1em;
    }
    
    .heroes a {
      color: #333;
      text-decoration: none;
      background-color: #EEE;
      margin: .5em;
      padding: .3em 0;
      height: 1.6em;
      border-radius: 4px;
      display: block;
      width: 100%;
    }
    
    .heroes a:hover {
      color: #2c3a41;
      background-color: #e6e6e6;
    }
    
    .heroes a:active {
      background-color: #525252;
      color: #fafafa;
    }
    
    .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

  • 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 for="hero-name">Hero name: </label>
        <input id="hero-name" [(ngModel)]="hero.name" placeholder="Hero name"/>
      </div>
      <button type="button" (click)="goBack()">go back</button>
    </div>
  • 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 | undefined;
    
      constructor(
        private route: ActivatedRoute,
        private heroService: HeroService,
        private location: Location
      ) {}
    
      ngOnInit(): void {
        this.getHero();
      }
    
      getHero(): void {
        const id = Number(this.route.snapshot.paramMap.get('id'));
        this.heroService.getHero(id)
          .subscribe(hero => this.hero = hero);
      }
    
      goBack(): void {
        this.location.back();
      }
    }
  • src/app/hero-detail/hero-detail.component.css
  • /* HeroDetailComponent's private CSS styles */
    label {
      color: #435960;
      font-weight: bold;
    }
    input {
      font-size: 1em;
      padding: .5rem;
    }
    button {
      margin-top: 20px;
      background-color: #eee;
      padding: 1rem;
      border-radius: 4px;
      font-size: 1rem;
    }
    button:hover {
      background-color: #cfd8dc;
    }
    button:disabled {
      background-color: #eee;
      color: #ccc;
      cursor: auto;
    }

小結(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)