填寫這份《一分鐘調查》,幫我們(開發組)做得更好!去填寫Home

用路由新增導航支援

Add navigation with routing

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

There are new requirements for the Tour of Heroes app:

  • 新增一個儀表盤檢視。

    Add a Dashboard view.

  • 新增在英雄列表儀表盤檢視之間導航的能力。

    Add the ability to navigate between the Heroes and Dashboard views.

  • 無論在哪個檢視中點選一個英雄,都會導航到該英雄的詳情頁。

    When users click a hero name in either view, navigate to a detail view of the selected hero.

  • 在郵件中點選一個深連結,會直接開啟一個特定英雄的詳情檢視。

    When users click a deep link in an email, open the detail view for a particular hero.

要檢視本頁所講的範例程式,參閱現場演練 / 下載範例

For the sample application that this page describes, see the現場演練 / 下載範例.

完成時,使用者就能像這樣在應用中導航:

When you’re done, users will be able to navigate the application like this:

新增 AppRoutingModule

Add the AppRoutingModule

在 Angular 中,最好在一個獨立的最上層模組中載入和配置路由器,它專注於路由功能,然後由根模組 AppModule 匯入它。

In Angular, the best practice is to load and configure the router in a separate, top-level module that is dedicated to routing and imported by the root AppModule.

按照慣例,這個模組類別的名字叫做 AppRoutingModule,並且位於 src/app 下的 app-routing.module.ts 檔案中。

By convention, the module class name is AppRoutingModule and it belongs in the app-routing.module.ts in the src/app folder.

使用 CLI 產生它。

Use the CLI to generate it.

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

--flat 把這個檔案放進了 src/app 中,而不是單獨的目錄中。
--module=app 告訴 CLI 把它註冊到 AppModuleimports 陣列中。

--flat puts the file in src/app instead of its own folder.
--module=app tells the CLI to register it in the imports array of the AppModule.

產生的檔案是這樣的:

The generated file looks like this:

src/app/app-routing.module.ts (generated)
      
      import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

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

把它替換為如下程式碼:

Replace it with the following:

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

首先,app-routing.module.ts 會匯入 RouterModuleRoutes,以便該應用具有路由功能。配置好路由後,接著匯入 HeroesComponent,它將告訴路由器要去什麼地方。

First, the app-routing.module.ts file imports RouterModule and Routes so the application can have routing functionality. The next import, HeroesComponent, will give the Router somewhere to go once you configure the routes.

注意,對 CommonModule 的參考和 declarations 陣列不是必要的,因此它們不再是 AppRoutingModule 的一部分。以下各節將詳細介紹 AppRoutingModule 的其餘部分。

Notice that the CommonModule references and declarations array are unnecessary, so are no longer part of AppRoutingModule. The following sections explain the rest of the AppRoutingModule in more detail.

路由

Routes

該檔案的下一部分是你的路由配置。 Routes 告訴路由器,當用戶單擊連結或將 URL 貼上進瀏覽器位址列時要顯示哪個檢視。

The next part of the file is where you configure your routes. Routes tell the Router which view to display when a user clicks a link or pastes a URL into the browser address bar.

由於 app-routing.module.ts 已經匯入了 HeroesComponent,因此你可以直接在 routes 陣列中使用它:

Since app-routing.module.ts already imports HeroesComponent, you can use it in the routes array:

src/app/app-routing.module.ts
      
      const routes: Routes = [
  { path: 'heroes', component: HeroesComponent }
];
    

典型的 Angular Route 具有兩個屬性:

A typical Angular Route has two properties:

  • path: 用來匹配瀏覽器位址列中 URL 的字串。

    path: a string that matches the URL in the browser address bar.

  • component: 導航到該路由時,路由器應該建立的元件。

    component: the component that the router should create when navigating to this route.

這會告訴路由器把該 URL 與 path:'heroes' 匹配。 如果網址類似於 localhost:4200/heroes 就顯示 HeroesComponent

This tells the router to match that URL to path: 'heroes' and display the HeroesComponent when the URL is something like localhost:4200/heroes.

RouterModule.forRoot()

@NgModule 元資料會初始化路由器,並開始監聽瀏覽器地址的變化。

The @NgModule metadata initializes the router and starts it listening for browser location changes.

下面的程式碼行將 RouterModule 新增到 AppRoutingModuleimports 陣列中,同時透過呼叫 RouterModule.forRoot() 來用這些 routes 配置它:

The following line adds the RouterModule to the AppRoutingModule imports array and configures it with the routes in one step by calling RouterModule.forRoot():

src/app/app-routing.module.ts
      
      imports: [ RouterModule.forRoot(routes) ],
    

這個方法之所以叫 forRoot(),是因為你要在應用的最上層配置這個路由器。 forRoot() 方法會提供路由所需的服務提供者和指令,還會基於瀏覽器的當前 URL 執行首次導航。

The method is called forRoot() because you configure the router at the application's root level. The forRoot() method supplies the service providers and directives needed for routing, and performs the initial navigation based on the current browser URL.

接下來,AppRoutingModule 匯出 RouterModule,以便它在整個應用程式中生效。

Next, AppRoutingModule exports RouterModule so it will be available throughout the app.

src/app/app-routing.module.ts (exports array)
      
      exports: [ RouterModule ]
    

新增路由出口 RouterOutlet

Add RouterOutlet

開啟 AppComponent 的範本,把 <app-heroes> 元素替換為 <router-outlet> 元素。

Open the AppComponent template and replace the <app-heroes> element with a <router-outlet> element.

src/app/app.component.html (router-outlet)
      
      <h1>{{title}}</h1>
<router-outlet></router-outlet>
<app-messages></app-messages>
    

AppComponent 的範本不再需要 <app-heroes>,因為只有當用戶導航到這裡時,才需要顯示 HeroesComponent

The AppComponent template no longer needs <app-heroes> because the application will only display the HeroesComponent when the user navigates to it.

<router-outlet> 會告訴路由器要在哪裡顯示路由的檢視。

The <router-outlet> tells the router where to display routed views.

能在 AppComponent 中使用 RouterOutlet,是因為 AppModule 匯入了 AppRoutingModule,而 AppRoutingModule 中匯出了 RouterModule。 在本課程開始時你執行的那個 ng generate 命令添加了這個匯入,是因為 --module=app 標誌。如果你手動建立 app-routing.module.ts 或使用了 CLI 之外的工具,你就要把 AppRoutingModule 匯入到 app.module.ts 中,並且把它新增到 NgModuleimports 陣列中。

The RouterOutlet is one of the router directives that became available to the AppComponent because AppModule imports AppRoutingModule which exported RouterModule. The ng generate command you ran at the start of this tutorial added this import because of the --module=app flag. If you manually created app-routing.module.ts or used a tool other than the CLI to do so, you'll need to import AppRoutingModule into app.module.ts and add it to the imports array of the NgModule.

試試看

Try it

你的 CLI 命令應該仍在執行吧。

You should still be running with this CLI command.

      
      ng serve
    

瀏覽器應該重新整理,並顯示著應用的標題,但是沒有顯示英雄列表。

The browser should refresh and display the application title but not the list of heroes.

看看瀏覽器的位址列。 URL 是以 / 結尾的。 而到 HeroesComponent 的路由路徑是 /heroes

Look at the browser's address bar. The URL ends in /. The route path to HeroesComponent is /heroes.

在位址列中把 /heroes 追加到 URL 後面。你應該能看到熟悉的主從結構的英雄顯示介面。

Append /heroes to the URL in the browser address bar. You should see the familiar heroes master/detail view.

理想情況下,使用者應該能透過點選連結進行導航,而不用被迫把路由的 URL 貼上到位址列。

Ideally, users should be able to click a link to navigate rather than pasting a route URL into the address bar.

新增一個 <nav> 元素,並在其中放一個連結 <a> 元素,當點選它時,就會觸發一個到 HeroesComponent 的導航。 修改過的 AppComponent 範本如下:

Add a <nav> element and, within that, an anchor element that, when clicked, triggers navigation to the HeroesComponent. The revised AppComponent template looks like this:

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",路由器會用它來匹配出指向 HeroesComponent 的路由。 routerLinkRouterLink 指令的選擇器,它會把使用者的點選轉換為路由器的導航操作。 它是 RouterModule 中的另一個公共指令。

A routerLink attribute is set to "/heroes", the string that the router matches to the route to HeroesComponent. The routerLink is the selector for the RouterLink directive that turns user clicks into router navigations. It's another of the public directives in the RouterModule.

重新整理瀏覽器,顯示出了應用的標題和指向英雄列表的連結,但並沒有顯示英雄列表。

The browser refreshes and displays the application title and heroes link, but not the heroes list.

點選這個連結。位址列變成了 /heroes,並且顯示出了英雄列表。

Click the link. The address bar updates to /heroes and the list of heroes appears.

從下面的 最終程式碼中把私有 CSS 樣式新增到 app.component.css 中,可以讓導航連結變得更好看一點。

Make this and future navigation links look better by adding private CSS styles to app.component.css as listed in the final code review below.

新增儀表盤檢視

Add a dashboard view

當有多個檢視時,路由會更有價值。不過目前還只有一個英雄列表檢視。

Routing makes more sense when there are multiple views. So far there's only the heroes view.

使用 CLI 新增一個 DashboardComponent

Add a DashboardComponent using the CLI:

      
      ng generate component dashboard
    

CLI 生成了 DashboardComponent 的相關檔案,並把它宣告到 AppModule 中。

The CLI generates the files for the DashboardComponent and declares it in AppModule.

把這三個檔案中的內容改成這樣:

Replace the default file content in these three files as follows:

      
      <h2>Top Heroes</h2>
<div class="heroes-menu">
  <a *ngFor="let hero of heroes">
    {{hero.name}}
  </a>
</div>
    

這個範本用來表示由英雄名字連結組成的一個陣列。

The template presents a grid of hero name links.

  • *ngFor 複寫器為元件的 heroes 陣列中的每個條目建立了一個連結。

    The *ngFor repeater creates as many links as are in the component's heroes array.

  • 這些連結被 dashboard.component.css 中的樣式格式化成了一些色塊。

    The links are styled as colored blocks by the dashboard.component.css.

  • 這些連結還沒有指向任何地方,但很快就會了

    The links don't go anywhere yet but they will shortly.

這個類別HeroesComponent 類別很像。

The class is similar to the HeroesComponent class.

  • 它定義了一個 heroes 陣列屬性。

    It defines a heroes array property.

  • 它的建構函式希望 Angular 把 HeroService 注入到私有的 heroService 屬性中。

    The constructor expects Angular to inject the HeroService into a private heroService property.

  • ngOnInit() 生命週期鉤子中呼叫 getHeroes()

    The ngOnInit() lifecycle hook calls getHeroes().

這個 getHeroes() 函式會擷取第 2 到 第 5 位英雄,也就是說只返回四個最上層英雄(第二,第三,第四和第五)。

This getHeroes() returns the sliced list of heroes at positions 1 and 5, returning only four of the Top Heroes (2nd, 3rd, 4th, and 5th).

src/app/dashboard/dashboard.component.ts
      
      getHeroes(): void {
  this.heroService.getHeroes()
    .subscribe(heroes => this.heroes = heroes.slice(1, 5));
}
    

新增儀表盤路由

Add the dashboard route

要導航到儀表盤,路由器中就需要一個相應的路由。

To navigate to the dashboard, the router needs an appropriate route.

DashboardComponent 匯入到 app-routing-module.ts 中。

Import the DashboardComponent in the app-routing-module.ts file.

src/app/app-routing.module.ts (import DashboardComponent)
      
      import { DashboardComponent } from './dashboard/dashboard.component';
    

把一個指向 DashboardComponent 的路由新增到 routes 陣列中。

Add a route to the routes array that matches a path to the DashboardComponent.

src/app/app-routing.module.ts
      
      { path: 'dashboard', component: DashboardComponent },
    

新增預設路由

Add a default route

當應用啟動時,瀏覽器的位址列指向了網站的根路徑。 它沒有匹配到任何現存路由,因此路由器也不會導航到任何地方。 <router-outlet> 下方是空白的。

When the application starts, the browser's address bar points to the web site's root. That doesn't match any existing route so the router doesn't navigate anywhere. The space below the <router-outlet> is blank.

要讓應用自動導航到這個儀表盤,請把下列路由新增到 routes 陣列中。

To make the application navigate to the dashboard automatically, add the following route to the routes array.

src/app/app-routing.module.ts
      
      { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
    

這個路由會把一個與空路徑“完全匹配”的 URL 重新導向到路徑為 '/dashboard' 的路由。

This route redirects a URL that fully matches the empty path to the route whose path is '/dashboard'.

瀏覽器重新整理之後,路由器載入了 DashboardComponent,並且瀏覽器的位址列會顯示出 /dashboard 這個 URL。

After the browser refreshes, the router loads the DashboardComponent and the browser address bar shows the /dashboard URL.

應該允許使用者透過點選頁面頂部導航區的各個連結在 DashboardComponentHeroesComponent 之間來回導航。

The user should be able to navigate back and forth between the DashboardComponent and the HeroesComponent by clicking links in the navigation area near the top of the page.

把儀表盤的導航連結新增到殼元件 AppComponent 的範本中,就放在 Heroes 連結的前面。

Add a dashboard navigation link to the AppComponent shell template, just above the Heroes link.

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>
    

重新整理瀏覽器,你就能透過點選這些連結在這兩個檢視之間自由導航了。

After the browser refreshes you can navigate freely between the two views by clicking the links.

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

The HeroDetailComponent displays details of a selected hero. At the moment the HeroDetailComponent is only visible at the bottom of the HeroesComponent

使用者應該能透過三種途徑看到這些詳情。

The user should be able to get to these details in three ways.

  1. 透過在儀表盤中點選某個英雄。

    By clicking a hero in the dashboard.

  2. 透過在英雄列表中點選某個英雄。

    By clicking a hero in the heroes list.

  3. 透過把一個“深連結” URL 貼上到瀏覽器的位址列中來指定要顯示的英雄。

    By pasting a "deep link" URL into the browser address bar that identifies the hero to display.

在這一節,你將能導航到 HeroDetailComponent,並把它從 HeroesComponent 中解放出來。

In this section, you'll enable navigation to the HeroDetailComponent and liberate it from the HeroesComponent.

HeroesComponent 中刪除英雄詳情

Delete hero details from HeroesComponent

當用戶在 HeroesComponent 中點選某個英雄條目時,應用應該能導航到 HeroDetailComponent,從英雄列表檢視切換到英雄詳情檢視。 英雄列表檢視將不再顯示,而英雄詳情檢視要顯示出來。

When the user clicks a hero item in the HeroesComponent, the application should navigate to the HeroDetailComponent, replacing the heroes list view with the hero detail view. The heroes list view should no longer show hero details as it does now.

開啟 HeroesComponent 的範本檔案(heroes/heroes.component.html),並從底部刪除 <app-hero-detail> 元素。

Open the HeroesComponent template (heroes/heroes.component.html) and delete the <app-hero-detail> element from the bottom.

目前,點選某個英雄條目還沒有反應。不過當你啟用了到 HeroDetailComponent 的路由之後,很快就能修復它

Clicking a hero item now does nothing. You'll fix that shortly after you enable routing to the HeroDetailComponent.

新增英雄詳情檢視

Add a hero detail route

要導航到 id11 的英雄的詳情檢視,類似於 ~/detail/11 的 URL 將是一個不錯的 URL。

A URL like ~/detail/11 would be a good URL for navigating to the Hero Detail view of the hero whose id is 11.

開啟 app-routing.module.ts 並匯入 HeroDetailComponent

Open app-routing.module.ts and import HeroDetailComponent.

src/app/app-routing.module.ts (import HeroDetailComponent)
      
      import { HeroDetailComponent } from './hero-detail/hero-detail.component';
    

然後把一個引數化路由新增到 routes 陣列中,它要匹配指向英雄詳情檢視的路徑。

Then add a parameterized route to the routes array that matches the path pattern to the hero detail view.

src/app/app-routing.module.ts
      
      { path: 'detail/:id', component: HeroDetailComponent },
    

path 中的冒號(:)表示 :id 是一個佔位符,它表示某個特定英雄的 id

The colon (:) in the path indicates that :id is a placeholder for a specific hero id.

此刻,應用中的所有路由都就緒了。

At this point, all application routes are in place.

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 中的英雄連線還沒有反應。

The DashboardComponent hero links do nothing at the moment.

路由器已經有一個指向 HeroDetailComponent 的路由了, 修改儀表盤中的英雄連線,讓它們透過引數化的英雄詳情路由進行導航。

Now that the router has a route to HeroDetailComponent, fix the dashboard hero links to navigate via the parameterized dashboard route.

src/app/dashboard/dashboard.component.html (hero links)
      
      <a *ngFor="let hero of heroes"
  routerLink="/detail/{{hero.id}}">
  {{hero.name}}
</a>
    

你正在 *ngFor 複寫器中使用 Angular 的插值繫結來把當前迭代的 hero.id 插入到每個 routerLink中。

You're using Angular interpolation binding within the *ngFor repeater to insert the current iteration's hero.id into each routerLink.

HeroesComponent 中的這些英雄條目都是 <li> 元素,它們的點選事件都繫結到了元件的 onSelect() 方法中。

The hero items in the HeroesComponent are <li> elements whose click events are bound to the component's onSelect() method.

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>)和名字包裹進一個 <a> 元素中, 並且像儀表盤的範本中那樣為這個 <a> 元素新增一個 routerLink 屬性。

Strip the <li> back to just its *ngFor, wrap the badge and name in an anchor element (<a>), and add a routerLink attribute to the anchor that is the same as in the dashboard template

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),讓列表恢復到以前的外觀。 修改後的樣式表參閱本指南底部的最終程式碼

You'll have to fix the private stylesheet (heroes.component.css) to make the list look as it did before. Revised styles are in the final code review at the bottom of this guide.

移除死程式碼(可選)

Remove dead code (optional)

雖然 HeroesComponent 類別仍然能正常工作,但 onSelect() 方法和 selectedHero 屬性已經沒用了。

While the HeroesComponent class still works, the onSelect() method and selectedHero property are no longer used.

最好清理掉它們,將來你會體會到這麼做的好處。 下面是刪除了死程式碼之後的類別。

It's nice to tidy up and you'll be grateful to yourself later. Here's the class after pruning away the dead code.

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

Routable HeroDetailComponent

以前,父元件 HeroesComponent 會設定 HeroDetailComponent.hero 屬性,然後 HeroDetailComponent 就會顯示這個英雄。

Previously, the parent HeroesComponent set the HeroDetailComponent.hero property and the HeroDetailComponent displayed the hero.

HeroesComponent 已經不會再那麼做了。 現在,當路由器會在響應形如 ~/detail/11 的 URL 時建立 HeroDetailComponent

HeroesComponent doesn't do that anymore. Now the router creates the HeroDetailComponent in response to a URL such as ~/detail/11.

HeroDetailComponent 需要從一種新的途徑獲取要顯示的英雄。 本節會講解如下操作:

The HeroDetailComponent needs a new way to obtain the hero-to-display. This section explains the following:

  • 獲取建立本元件的路由

    Get the route that created it

  • 從這個路由中提取出 id

    Extract the id from the route

  • 透過 HeroService 從伺服器上獲取具有這個 id 的英雄資料。

    Acquire the hero with that id from the server via the HeroService

先新增下列匯入語句:

Add the following imports:

src/app/hero-detail/hero-detail.component.ts
      
      import { ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common';

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

然後把 ActivatedRouteHeroServiceLocation 服務注入到建構函式中,將它們的值儲存到私有變數裡:

Inject the ActivatedRoute, HeroService, and Location services into the constructor, saving their values in private fields:

src/app/hero-detail/hero-detail.component.ts
      
      constructor(
  private route: ActivatedRoute,
  private heroService: HeroService,
  private location: Location
) {}
    

ActivatedRoute儲存著到這個 HeroDetailComponent 實例的路由資訊。 這個元件對從 URL 中提取的路由引數感興趣。 其中的 id 引數就是要顯示的英雄的 id

The ActivatedRouteholds information about the route to this instance of the HeroDetailComponent. This component is interested in the route's parameters extracted from the URL. The "id" parameter is the id of the hero to display.

HeroService從遠端伺服器獲取英雄資料,本元件將使用它來獲取要顯示的英雄。

The HeroServicegets hero data from the remote server and this component will use it to get the hero-to-display.

location是一個 Angular 的服務,用來與瀏覽器打交道。 稍後,你就會使用它來導航回上一個檢視。

The locationis an Angular service for interacting with the browser. You'll use it later to navigate back to the view that navigated here.

從路由引數中提取 id

Extract the id route parameter

ngOnInit() 生命週期鉤子 中呼叫 getHero(),程式碼如下:

In the ngOnInit() lifecycle hook call getHero() and define it as follows.

src/app/hero-detail/hero-detail.component.ts
      
      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 是一個路由資訊的靜態快照,抓取自元件剛剛建立完畢之後。

The route.snapshot is a static image of the route information shortly after the component was created.

paramMap 是一個從 URL 中提取的路由引數值的字典。 "id" 對應的值就是要獲取的英雄的 id

The paramMap is a dictionary of route parameter values extracted from the URL. The "id" key returns the id of the hero to fetch.

路由引數總會是字串。 JavaScript 的 Number 函式會把字串轉換成數字,英雄的 id 就是數字型別。

Route parameters are always strings. The JavaScript Number function converts the string to a number, which is what a hero id should be.

重新整理瀏覽器,應用掛了。出現一個編譯錯誤,因為 HeroService 沒有一個名叫 getHero() 的方法。 這就新增它。

The browser refreshes and the application crashes with a compiler error. HeroService doesn't have a getHero() method. Add it now.

新增 HeroService.getHero()

Add HeroService.getHero()

新增 HeroService,並在 getHeroes() 後面新增如下的 getHero() 方法,它接收 id 引數:

Open HeroService and add the following getHero() method with the id after the getHeroes() method:

src/app/hero.service.ts (getHero)
      
      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);
}
    

注意,反引號 ( ` ) 用於定義 JavaScript 的 範本字串字面量,以便嵌入 id

Note the backticks ( ` ) that define a JavaScript template literal for embedding the id.

getHeroes()一樣,getHero() 也有一個非同步函式簽名。 它用 RxJS 的 of() 函式返回一個 Observable 形式的模擬英雄資料

Like getHeroes(), getHero() has an asynchronous signature. It returns a mock hero as an Observable, using the RxJS of() function.

你將來可以用一個真實的 Http 請求來重新實現 getHero(),而不用修改呼叫了它的 HeroDetailComponent

You'll be able to re-implement getHero() as a real Http request without having to change the HeroDetailComponent that calls it.

試試看

Try it

重新整理瀏覽器,應用又恢復正常了。 你可以在儀表盤或英雄列表中點選一個英雄來導航到該英雄的詳情檢視。

The browser refreshes and the application is working again. You can click a hero in the dashboard or in the heroes list and navigate to that hero's detail view.

如果你在瀏覽器的位址列中貼上了 localhost:4200/detail/11,路由器也會導航到 id: 11 的英雄("Dr. Nice")的詳情檢視。

If you paste localhost:4200/detail/11 in the browser address bar, the router navigates to the detail view for the hero with id: 11, "Dr Nice".

回到原路

Find the way back

透過點選瀏覽器的後退按鈕,你可以回到英雄列表或儀表盤檢視,這取決於你從哪裡進入的詳情檢視。

By clicking the browser's back button, you can go back to the hero list or dashboard view, depending upon which sent you to the detail view.

如果能在 HeroDetail 檢視中也有這麼一個按鈕就更好了。

It would be nice to have a button on the HeroDetail view that can do that.

把一個後退按鈕新增到元件範本的底部,並且把它繫結到元件的 goBack() 方法。

Add a go back button to the bottom of the component template and bind it to the component's goBack() method.

src/app/hero-detail/hero-detail.component.html (back button)
      
      <button (click)="goBack()">go back</button>
    

在元件類別中新增一個 goBack() 方法,利用你以前注入的 Location 服務在瀏覽器的歷史棧中後退一步。

Add a goBack() method to the component class that navigates backward one step in the browser's history stack using the Location service that you injected previously.

src/app/hero-detail/hero-detail.component.ts (goBack)
      
      goBack(): void {
  this.location.back();
}
    

重新整理瀏覽器,並開始點選。 使用者能在應用中導航:從儀表盤到英雄詳情再回來,從英雄列表到 mini 版英雄詳情到英雄詳情,再回到英雄列表。

Refresh the browser and start clicking. Users can navigate around the app, from the dashboard to hero details and back, from heroes list to the mini detail to the hero details and back to the heroes again.

當你將一些私有 CSS 樣式新增到 hero-detail.component.css 裡之後,其細節看起來會更好,如下面的“檢視最終程式碼”標籤頁中所示。

The details will look better when you add the private CSS styles to hero-detail.component.css as listed in one of the "final code review" tabs below.

檢視最終程式碼

Final code review

本頁討論的程式碼檔案如下。

Here are the code files discussed on this page.

AppRoutingModuleAppModuleHeroService

AppRoutingModule, AppModule, and HeroService

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

AppComponent

      
      <h1>{{title}}</h1>
<nav>
  <a routerLink="/dashboard">Dashboard</a>
  <a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
<app-messages></app-messages>
    

DashboardComponent

      
      <h2>Top Heroes</h2>
<div class="heroes-menu">
  <a *ngFor="let hero of heroes"
    routerLink="/detail/{{hero.id}}">
    {{hero.name}}
  </a>
</div>
    

HeroesComponent

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

HeroDetailComponent

      
      <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 (click)="goBack()">go back</button>
</div>
    

小結

Summary

  • 添加了 Angular 路由器在各個不同元件之間導航。

    You added the Angular router to navigate among different components.

  • 你使用一些 <a> 連結和一個 <router-outlet>AppComponent 轉換成了一個導航用的殼元件。

    You turned the AppComponent into a navigation shell with <a> links and a <router-outlet>.

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

    You configured the router in an AppRoutingModule

  • 你定義了一些簡單路由、一個重新導向路由和一個引數化路由。

    You defined routes, a redirect route, and a parameterized route.

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

    You used the routerLink directive in anchor elements.

  • 你把一個緊耦合的主從檢視重構成了帶路由的詳情檢視。

    You refactored a tightly-coupled master/detail view into a routed detail view.

  • 你使用路由連結引數來導航到所選英雄的詳情檢視。

    You used router link parameters to navigate to the detail view of a user-selected hero.

  • 在多個元件之間共享了 HeroService 服務。

    You shared the HeroService among multiple components.