Angulra2 View建立 with VS2015 - Step6. Router_part1

Angulra2 View建立 with VS2015 - Step6. Router

切割 AppComponent

原本的應用會直接讀取 AppComponent 並顯示英雄列表

修改後的應用將會多一層包裝(shell),它會選擇儀表板英雄列表(Dashboard and Heroes)。

所以 AppComponent 應該只負責處理路徑(router)問題,接著把英雄列表的顯示功能,從 AppComponent 轉移到 HeroesComponent 中。

HeroesComponent

AppComponent 的工作已經轉移到 HeroesComponent 了,所以直接將它改名為 HeroesComponent ,接著重新建立新的 AppComponent

更改步驟如下:

  • app.component.ts 檔案(File)名稱改為 heroes.component.ts
  • AppComponent 類別(Class)名稱改為 HeroesComponent
  • my-app 選擇器(Selector)名稱改為 my-heroes
@Component({
  selector: 'my-heroes',
})
export class HeroesComponent implements OnInit {
}

 

 

重新建立 AppComponent

新的 AppComponent 會變成應用程式的外包裝(shell),接著把我們要指向的路徑放到下面的步驟中。

初始化步驟如下:

  1. 在 /app 底下新增一個 app.component.ts 檔案。
  2. 定義一個 AppComponent 的類別並 export 它,以便在 main.ts 啟動時能引用。
  3. export 應用程式的 title 屬性。
  4. 在類別上方增加 @Component 中繼資料裝飾器(metadata decorator),其中包含 my-app 選擇器(selector)。
  5. 在樣板(template)中增加一個 <h1> 標籤,接著綁定 title 屬性。
  6. 增加 HeroesComponent 組件到 directives 陣列中,以便 Angular 可以認識 <my-heroes> 標籤。
  7. 增加 HeroesServiceProviders 陣列中,因為每一個頁面都會需要用到它。
  8. import 所需要引用的東西。
import { Component }       from '@angular/core';
import { HeroService }     from './hero.service';
import { HeroesComponent } from './heroes.component';
@Component({
  selector: 'my-app',
  template: `
    <h1>{{title}}</h1>
    <my-heroes></my-heroes>
  `,
  directives: [HeroesComponent],
  providers: [
    HeroService
  ]
})
export class AppComponent {
  title = 'Tour of Heroes';
}
打開 heroes.component.ts 把 providers 陣列中的 HeroesService 移除。我們要把此服務提升到 AppComponent 中。

因為我們不希望同一組服務存在於兩個不同層級的應用程式中。
 

目前為止我們將 AppComponent 重構成一個新的 AppComponentHeroesComponent

 

 

增加Router

打開 index.html 並在 <head> 最上層的區域增加 <base href="/"> 語法。

Angular Router 是由多個服務( ROUTER_PROVIDERS )和多個指令( ROUTER_DIRECTIVES )以及一個路由設定( RouteConfig )所組成的,這邊我們直接都引用進來。 

 

應用程式還沒設定路由,因此在 app/ 底下建立一個 router.ts 檔案,這個檔案要做兩件事情:

  1. 設定路由
  2. 把這個路由導出(export)並增加到啟動程式(bootstrap)中

路由的工作是告訴路由器,當使用者點擊連結或者輸入網址時應該顯示哪個頁面。

接著開始設定第一個路由,連接到 HeroComponent 

import { provideRouter, RouterConfig }  from '@angular/router';
import { HeroesComponent } from './heroes.component';

const routes: RouterConfig = [
  {
    path: 'heroes',
    component: HeroesComponent
  }
];

export const APP_ROUTER_PROVIDERS = [
  provideRouter(routes)
];

這個 RouteConfig 是一個定義路由的陣列資料,目前先只定義一組路由,後續會再額外增加。

RouteConfig 主要結構包含兩個部分:

  • path:路由器會用它來比對,路由器所設定的路徑和網址列中當下的路徑,例:/heroes
  • component:當指向這個路由時,路由器需要建立的組件,例:HeroesComponent

 

讓路由器生效

組件路由器(Component Router)本身是一個服務。因此我們需要導入 APP_ROUTER_PROVIDERS (它包含了我們設定好的路由器),使用 import 的方式把它加進 main.ts 中。

import { bootstrap }    from '@angular/platform-browser-dynamic';

import { AppComponent } from './app.component';
import { APP_ROUTER_PROVIDERS } from './app.routes';

bootstrap(AppComponent, [
  APP_ROUTER_PROVIDERS
]);

 

Router Outlet

如果我們把路徑 /heroes 複製到網址列中,路由器會指向 'Heroes' 路由,並顯示 HeroesComponent 組件。但問題是該把它顯示在哪?

所以我們必須告訴它位置,所以我們把 <router-outlet> 標籤加進樣板(template)的下方。

RouterOutlet is one of the ROUTER_DIRECTIVES

當指向某個應用程式時,路由器就把組件顯示在 <router-outlet> 裡面。

 

路由器連接

我們當然不會真的讓使用者在網址列中輸入網址。而是在樣板上增加一個 <a> 的標籤,點擊後就會指向 HeroesComponent 組件。修改過的 app.component.ts 如下:

template: `
  <h1>{{title}}</h1>
  <a [routerLink]="['/heroes']">Heroes</a>
  <router-outlet></router-outlet>
`,
要注意的是,<a> 標籤中的 [routerLink] 綁定。我們把 RouterLink 指令( ROUTER_DIRECTIVES 中的另一個指令 )綁定到一個陣列。由它來告訴路由器,當使用者點擊連接時,應該指向哪裡。

刷新頁面後會發現到只剩原有的標題,而英雄列表則不見了,可以修改網址路徑為 /heroes 或點擊 heroes 連接。

由於我們是用VS2015,所以在初期環境設定時 Startup.cs 檔中就有預設路徑為 /index

在這個階段,AppComponent 程式碼如下:

import { Component } from '@angular/core';
import { ROUTER_DIRECTIVES } from '@angular/router';
import { HeroService }     from './hero.service';
@Component({
  selector: 'my-app',
  template: `
    <h1>{{title}}</h1>
    <a [routerLink]="['/heroes']">Heroes</a>
    <router-outlet></router-outlet>
  `,
  directives: [ROUTER_DIRECTIVES],
  providers: [
    HeroService
  ]
})
export class AppComponent {
  title = 'Tour of Heroes';
}

現在 AppComponent 上已經有了路由器,並能顯示指向的頁面。所以我們打算將這類的路由器組件從中分離出來。

 

 

增加一個儀錶板(Dashboard)

當我們有多個頁面時,路由才有意義。所以我們需要增加另一個頁面。

先建立一個基礎結構的 dashboard.component.ts ,後續會再增加實作內容。

import { Component } from '@angular/core';

@Component({
  selector: 'my-dashboard',
  template: '<h3>My Dashboard</h3>'
})
export class DashboardComponent { }

 

 

設定儀錶板路由

打開 app.router.ts 檔案,首先導入 DashboardComponent 類別,這樣就可以引用它了。

然後把 'Dashboard' 路由的定義加到 @RouteConfig 陣列中。

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

如果希望啟動時有預設路徑,如:/dashboard ,或者路徑為空時導向預設路徑。我們可以增加以下方法來重新指向。

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

 

接著在 app.component.ts 的樣板上增加一個儀表板的連接功能。

目前在 <nav> 標籤中放了兩個連接,後續會加上一些樣式,以利辨識。

template: `
  <h1>{{title}}</h1>
  <nav>
    <a [routerLink]="['/dashboard']" routerLinkActive="active">Dashboard</a>
    <a [routerLink]="['/heroes']" routerLinkActive="active">Heroes</a>
  </nav>
  <router-outlet></router-outlet>
`,

 

 

為了讓儀錶板變的簡單明瞭,我們從 dashboard.component.ts 中更改了樣板。

templateUrl: 'app/dashboard.component.html',

接著我們建立 dashboard.component.htnl 檔案

<h3>Top Heroes</h3>
<div class="grid grid-pad">
  <div *ngFor="let hero of heroes" (click)="gotoDetail(hero)" class="col-1-4">
    <div class="module hero">
      <h4>{{hero.name}}</h4>
    </div>
  </div>
</div>

這裡又使用了 *ngFor 來顯示英雄列表,還額外增加了一個 <div> 標籤來做美化。

其中的 (click) 綁定到了 gotoDetail 方法,接下來會開始調整這些方法。

 

共用 HeroService

在前面的章節中我們從 HeroesComponentproviders 陣列中移除了 HeroService ,並把它移到 AppComponentproviders 中。

所以在 DashboardComponent 組件中,Angular 會把它注入進來,我們就可以使用它了。

打開 dashboard.component.ts 檔案,把必要的 import 語法加進去。

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

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

接著我們必須實作 Onlint 介面,因為我們要在 Onlint 方法中初始化英雄資料。

需要導入 Hero 類別和 HeroService 類別來引用他們的資料,實作 DashboardComponent 類別如下

import { Component, OnInit } from '@angular/core';
import { Hero } from './hero';
import { HeroService } from './hero.service';

@Component({
    selector: 'my-dashboard',
    templateUrl: 'dashboard.component.html',
})
export class DashboardComponent implements OnInit {
    heroes: Hero[] = [];
    constructor(private heroService: HeroService) { }
    ngOnInit() {
        this.heroService.getHeroes()
            .then(heroes => this.heroes = heroes.slice(1, 5));
    }
    gotoDetail() { /* not implemented yet */ }
}