Angulra2 View建立 with VS2015 - Step6. Router_part2

Angulra2 View建立 with VS2015 - Step6. Router_part1

 

 

Router 章節前半部我們已經將基本功能實做出來,現在則是針對需求來做額外功能

雖然我們在 HeroesComponent 組件顯示了所選英雄的詳細資料,但是我們還沒有指向到 HeroDetailComponent 組見過,所以我們需求如下:

  1. 從儀錶板(Dashboard) 指向到選定的英雄。
  2. 從英雄列表(Heroes) 指向到選定的英雄。
  3. 把一個指向該英雄的連結複製到瀏覽器的網址列。

 

 

Routing to a hero detail

新路由特別的地方在於我們必須告訴 HeroDetailComponent 該顯示哪個英雄

父組件 HeroesComponent 透過綁定的方式來把一個英雄設為組件的 hero 屬性:

<my-hero-detail [hero]="selectedHero"></my-hero-detail>

我們可以把英雄的 id 加到 URL 中,當指向 id 為 11 的英雄時,我們預期的 URL 如下:

/detail/11

URL 中的 /detail/ 部分是固定不變的,但是結尾數字 id 部分會隨著選取目標的不同而改變。

因此我們把這部分表示成一個參數(parameter)token ,以便能取得英雄的 id

打開 app.router.ts 檔案,定義新的路由設定:

//記得要事先 import
import { HeroDetailComponent } from './hero-detail.component';


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

路徑中的冒號( : )表示 :id 是一個 placeholder ,當指向這個 HeroDetailComponent 組件時,它會被填入某個特定英雄的 id

 

修改 HeroDetailComponent

樣板的部分可以不用更改,我們會用原來的方式來顯示英雄資料。需要修改的原因在於如何取得英雄資料。

我們不會在從父組件的屬性綁定中取得資料。新的 HeroDetailComponent 應該從 ActivatedRoute 服務中取得 params 中的 id 參數,並透過 HeroService 服務得到指定 id 的英雄資料。

首先導入 ActivatedRoute 和 HeroService

import { ActivatedRoute } from '@angular/router';
import { HeroService } from './hero.service';

接著導入 Onlnit 和 OnDestory 介面,因為我們需要在 ngOnlnit 生命週期內呼叫 HeroService ,並且在 ngOnDestory 中清除對 parpms 的訂閱。

import { Component, OnInit, OnDestroy } from '@angular/core';

告訴這個類別,我們要實作 OnInit 和 OnDestroy 介面。

export class HeroDetailComponent implements OnInit, OnDestroy {

ngOnInit 生命週期中,從 RouterParams 服務中取得 id 參數,並使用 HeroService 來得到此 id 的英雄資料。

  ngOnInit() {
    this.sub = this.route.params.subscribe(params => {
      let id = +params['id'];
      this.heroService.getHero(id)
        .then(hero => this.hero = hero);
    });
  }

ngOnDestroy 生命週期中,我們取消了params 的訂閱。

ngOnDestroy() {
  this.sub.unsubscribe();
}

注意我們是如何呼叫 subscribe 方法,這將實現我們從 router 中擷取 id

this.sub = this.route.params.subscribe(params => {
  let id = +params['id'];
  this.heroService.getHero(id)
    .then(hero => this.hero = hero);
});

英雄的 id 型態是 numbe,而路由參數的值是 string,所以我們需要透過 JavaScript 的 ( + ) 來把參數的字串轉為數字。

接著我們發現 HeroService 中沒有一個叫 getHero() 的方法,所以增加一個方法然後透過 id 的方式來取得資料。

getHero(id: number) {
  return this.getHeroes()
             .then(heroes => heroes.find(hero => hero.id === id));
}

再來增加一個回到上一層的方法,goBack()

goBack() {
  window.history.back();
}
一般來說會建議回到上一層即可,用此方法很容易回到更上層的地方去,需特別注意。

接著把 goBack 方法綁定到樣板中。

<button (click)="goBack()">Back</button>

接著可以把樣板部分獨立到 hero-detail.component.html 中。

<div *ngIf="hero">
  <h2>{{hero.name}} details!</h2>
  <div>
    <label>id: </label>{{hero.id}}</div>
  <div>
    <label>name: </label>
    <input [(ngModel)]="hero.name" placeholder="name" />
  </div>
  <button (click)="goBack()">Back</button>
</div>

接著更新組件的中繼資料,用一個 templateUrl 來指向剛剛所建立的樣板檔案。

templateUrl: 'app/hero-detail.component.html',

以下是 HeroDetailComponent 完整程式;

// 原始 hero-detail.component.ts
import { Component, Input } from '@angular/core';
import { Hero } from './hero';
@Component({
    selector: 'my-hero-detail',
    template: `
    <div *ngIf="hero">
      <h2>{{hero.name}} details!</h2>
      <div><label>id: </label>{{hero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ngModel)]="hero.name" placeholder="name"/>
      </div>
    </div>
  `
})
export class HeroDetailComponent {
    @Input()
    hero: Hero;
}


// 修改後如下
import { Hero } from './hero';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { HeroService } from './hero.service';

@Component({
    selector: 'my-hero-detail',
    templateUrl: 'hero-detail.component.html',
})

export class HeroDetailComponent implements OnInit, OnDestroy {
    hero: Hero;
    sub: any;

    constructor(
        private heroService: HeroService,
        private route: ActivatedRoute) {
    }

    ngOnInit() {
        this.sub = this.route.params.subscribe(params => {
            let id = +params['id'];
            this.heroService.getHero(id)
                .then(hero => this.hero = hero);
        });
    }

    ngOnDestroy() {
        this.sub.unsubscribe();
    }

    goBack() {
        window.history.back();
    }
    
}