天天看點

Angular2官網項目 (4)--路由

行動計劃

把<code>AppComponent</code>變成應用程式的“殼”,它隻'處理導航'

把現在由<code>AppComponent</code>關注的英雄們移到一個獨立的<code>GeneralComponent</code>中

添加路由

建立一個新的<code>DashboardComponent</code>元件

把儀表盤加入導航結構中

路由是導航的另一個名字。路由器就是從一個視圖導航到另一個視圖的機制。

拆分Appcomponent.

        現在的應用會加載<code>AppComponent</code>元件,并且立刻顯示出英雄清單。

        我們修改後的應用将提供一個殼,它會選擇儀表盤和英雄清單視圖之一,然後預設顯示它。

<code>  AppComponent</code>元件應該隻處理導航。 我們來把英雄清單的顯示職責,從<code>AppComponent</code>移到<code>GeneralComponent</code>元件中。

GeneralComponent元件

<code>AppComponent</code>的職責已經被移交給<code>GeneralComponent</code>了。 與其把<code>AppComponent</code>中所有的東西都搬過去,不如索性把它改名為<code>GeneralComponent</code>,然後單獨建立一個新的<code>AppComponent</code>殼。

        步驟:

                1.把app.component.ts 和app.component.html 和app.component.scss移入到General檔案夾下。

                2.app改為generals

                3.類名AppComponent改為GeneralsComponent

                4.選擇器名稱app-root改為my-general

建立AppComponent

新的<code>AppComponent</code>将成為應用的“殼”。 它将在頂部放一些導航連結,并且把我們要導航到的頁面放在下面的顯示區中。

                     在./app下建立app.component.ts

添加支援性的<code>import</code>語句。

定義一個導出的 <code>AppComponent</code>類。

在類的上方添加<code>@Component</code>中繼資料裝飾器,裝飾器帶有app-root選擇器。

将下面的項目從<code>HeroesComponent</code>移到<code>AppComponent</code>:

<code>title</code>類屬性

<code>@Component</code>模闆中的<code>&lt;h1&gt;</code>标簽,它包含了對<code>title</code>屬性的綁定。

在模闆的标題下面添加<code>&lt;my-heroes&gt;</code>标簽,以便我們仍能看到英雄清單。

添加<code><code>GeneralComponent</code></code>元件到根子產品的<code>declarations</code>數組中,以便 Angular 能認識<code>&lt;my-generals&gt;</code>标簽。

添加<code>GeneralService</code>到<code>AppModule</code>的<code>providers</code>數組中,因為我們的每一個視圖都需要它。

從<code><code><code>GeneralComponent</code></code></code>的<code>providers</code>數組中移除<code>GeneralService</code>,因為它被提到子產品了。

為<code>AppComponent</code>添加一些<code>import</code>語句。

./app/app.component.ts

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

@Component({

  selector: 'app-root',

  template: `

    &lt;my-generals&gt;&lt;/my-generals&gt;

  `

})

export class AppComponent {

}

./app/app.module.ts

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

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

import { FormsModule }   from '@angular/forms';

import {GeneralService} from'./general.service';

//注冊指令---&gt;用法類似于元件

import {HighlightDirective} from '../directive/drag.directive';

//測試管道所用的元件

import {HeroBirthdayComponent} from "../Pipes/hero-birthday1.component";

import {ExponentialStrengthPipe} from "../Pipes/exponential-strength.pipe"

import {GeneralsComponent} from './general/generals.component';

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

import {GeneralDetailComponent} from './general/general-detail.component';

@NgModule({

  //列出程式中的元件

  declarations: [

    AppComponent,

    GeneralDetailComponent,

      GeneralsComponent,

    // HighlightDirective,

    // HeroBirthdayComponent,

    // ExponentialStrengthPipe

  ],

  //導入子產品

  imports: [

  /*BrowserModule,每個運作在浏覽器中的應用都必須導入它。

       BrowserModule注冊了一些關鍵的應用“服務”提供商。 它還包括了一些通用的指令,

       例如NgIf和NgFor,是以這些指令在該子產品的任何元件模闆中都是可用的。 

   */

    BrowserModule,

  //雙向資料綁定依賴的子產品

    FormsModule

  providers: [GeneralService],

  /*@NgModule.bootstrap屬性把這個AppComponent标記為引導 (bootstrap) 元件。 

    當 Angular 引導應用時,它會在 DOM 中渲染AppComponent,并把結果放進index.html的&lt;app-root&gt;元素标記内部。  

  */

  bootstrap: [AppComponent]

export class AppModule { }

./app/general/generals.component.ts

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

import {General}  from "../../bean/General";

import {GeneralService} from'../general.service';

  selector: 'my-generals',

  templateUrl: './generals.component.html',

  styleUrls: ['./generals.component.scss']

export class GeneralsComponent implements OnInit {

  title = 'MY General';

  // generals:General[]=Generals;

   color="pink";

   constructor(private generalService:GeneralService ){

   }

 selectGeneral:General;

  generals:General[];

  getGenerals():void{

    this.generalService.getGenerals()

    .then(generals=&gt;this.generals=generals);

  }

  ngOnInit(){

      this.getGenerals();

  oSelect(item:General):void{

   this.selectGeneral=item;

我們希望在使用者點選按鈕之後才顯示英雄清單,而不是自動顯示。 換句話說,我們希望使用者能“導航”到英雄清單。

我們要使用 Angular 路由器進行導航。

    打開src/index.html

            確定它的<code>&lt;head&gt;</code>區頂部有一個<code>&lt;base href="..."&gt;</code>元素(或動态生成該元素的腳本)

            a.導入路由所需要的子產品

                    import { RouterModule }   from '@angular/router';

            b.在imports下寫入       

     RouterModule.forRoot([

            {

              path: 'generals',

              component: GeneralsComponent

            }

          ])

路由定義包括以下部分:

Path: 路由器會用它來比對浏覽器位址欄中的位址,如<code>generals</code>。

Component: 導航到此路由時,路由器需要建立的元件(<code>GeneralsComponent</code>)。

這裡使用了<code>forRoot()</code>方法,因為我們是在應用根部提供配置好的路由器。 <code>forRoot()</code>方法提供了路由需要的“”路由服務提供商和指令”,并基于目前浏覽器 URL 初始化導航。

我們當然不會真讓使用者往位址欄中粘貼路由的 URL, 而應該在模闆中的什麼地方添加一個錨标簽。點選時,就會導航到<code>GeneralsComponent</code>元件。

  &lt;a routerLink="/generals"&gt;Generals&lt;/a&gt;

由于這個連結不是動态的,我們隻要用一次性綁定的方式綁定到路由的路徑 (path) 就行了。 回來看路由配置表,我們清楚的看到,這個路徑 —— <code>'/heroes'</code>就是指向<code>HeroesComponent</code>的那個路由的路徑。

現在./app/app.component.ts

   &lt;a routerLink="/generals"&gt;Generals&lt;/a&gt;

   &lt;router-outlet&gt;&lt;/router-outlet&gt;

AppComponent現在加上了路由器,并能顯示路由到的視圖了。 是以,為了把它從其它種類的元件中區分出來,我們稱這類元件為路由器元件。

當我們有多個視圖的時候,路由才有意義。是以我們需要另一個視圖。先建立一個<code>DashboardComponent</code>的占位符,讓使用者可以導航到它或從它導航出來。

    在./src/app下建立dashboard檔案夾

            在這下面建立dashboard.component.ts

  selector: 'my-dashboard',

  template: '&lt;h3&gt;My Dashboard&lt;/h3&gt;'

export class DashboardComponent { }

我們先不實作他

 {

          path: 'dashboard',

          component: DashboardComponent

        },

然後還得把<code>DashboardComponent</code>添加到<code>AppModule</code>的<code>declarations</code>數組中。

  DashboardComponent

<code></code>

  {

          path: '',

          redirectTo: '/dashboard',

          pathMatch: 'full'

在模闆上添加一個到儀表盤的導航連結,就放在Generals(英雄清單)連結的上方。

    &lt;a routerLink="/dashboard"&gt;Dashboard&lt;/a&gt;

重新整理浏覽器。應用顯示出了儀表盤,并可以在儀表盤和英雄清單之間導航了。

       讓儀表盤變得有趣。

把中繼資料中的<code>template</code>屬性替換為<code>templateUrl</code>屬性,它将指向一個新的模闆檔案。

templateUrl: './dashboard.component.html'

增加styleUrls:['./dashboard.component.scss']

dashboard.component.htm  

&lt;h3&gt;Top Generals&lt;/h3&gt;

&lt;div&gt;

  &lt;div *ngFor="let general of generals" class="col-1-4"&gt;

    &lt;div&gt;

      &lt;h4&gt;`general`.`name`&lt;/h4&gt;

    &lt;/div&gt;

  &lt;/div&gt;

&lt;/div&gt;

我們再次使用<code>*ngFor</code>來在英雄清單上疊代,并顯示它們的名字。 還添加了一個額外的<code>&lt;div&gt;</code>元素,來幫助稍後的美化工作。

dashboard.component.ts

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

  styleUrls:['./dashboard.component.scss']

export class DashboardComponent implements OnInit { 

generals:General[];

constructor(private generalService:GeneralService){}

ngOnInit(){

this.generalService.getGenerals().then(generals=&gt;this.generals=generals.slice(1,5));

我們在之前的<code>GeneralsComponent</code>中也看到過類似的邏輯:

建立一個<code>generals</code>數組屬性。

在構造函數中注入<code>GeneralService</code>,并且把它儲存在一個私有的<code>generalService</code>字段中。

在 Angular 的<code>ngOnInit</code>生命周期鈎子裡面調用服務來獲得英雄資料。

在儀表盤中我們用<code>Array.slice</code>方法提取了四個英雄(第2、3、4、5個)。

重新整理浏覽器,在這個新的儀表盤中就看到了四個英雄。

雖然我們在<code>HeroesComponent</code>元件的底部顯示了所選英雄的詳情, 但使用者還沒法導航到<code>GeneralDetailComponent</code>元件。我們可以用下列方式導航到<code>GeneralDetailComponent</code>:

從Dashboard(儀表盤)導航到一個標明的英雄。

從Generals(英雄清單)導航到一個標明的英雄。

把一個指向該英雄的“深連結” URL 粘貼到浏覽器的位址欄。

我們将在<code>app.module.ts</code>中添加一個到<code>GeneralDetailComponent</code>的路由,也就是配置其它路由的地方。

這個新路由的不尋常之處在于,我們必須告訴<code>GeneralDetailComponent</code>該顯示哪個英雄。 之前,我們不需要告訴<code>GeneralsComponent</code>元件和<code>DashboardComponent</code>元件任何東西

我們可以把英雄的<code>id</code>添加到 URL 中。當導航到一個<code>id</code>為 11 的英雄時,我們期望的 URL 是這樣的:

    /detail/11

URL中的<code>/detail/</code>部分是固定不變的,但結尾的數字<code>id</code>部分會随着英雄的不同而變化。 我們要把路由中可變的那部分表示成一個參數 (parameter) 或令牌 (token) ,代表英雄的<code>id</code>。

          path: 'detail/:id',

          component: GeneralDetailComponent

路徑中的冒号 (:) 表示<code>:id</code>是一個占位符,當導航到這個<code>GeneralDetailComponent</code>元件時,它将被填入一個特定英雄的<code>id</code>。

模闆不用修改,我們會用原來的方式顯示英雄。導緻這次大修的原因是如何獲得這個英雄的資料。

先添加下列導入語句:

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

import { ActivatedRoute, ParamMap } from '@angular/router';

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

import 'rxjs/add/operator/switchMap';

  constructor( private generalService: GeneralService,

  private route: ActivatedRoute,

  private location: Location){}

 ngOnInit(){

    this.route.paramMap

    .switchMap((params: ParamMap) =&gt; this.generalService.getGeneral(+params.get('id')))

    .subscribe(general =&gt; this.general = general);

在前面的代碼片段中GeneralService沒有<code>getGeneral()</code>方法。要解決這個問題,請打開<code>GeneralService</code>并添加一個<code><code>getGenera</code>()</code>方法,它會根據<code>id</code>從<code><code>getGenerals</code>()</code>中過濾英雄清單。

etGeneral(id: number): Promise&lt;General&gt; {

   return this.getGenerals()

             .then(generals =&gt; generals.find(general =&gt; general.id === id));

 goBack(): void {

   this.location.back();

然後,我們通過一個事件綁定把此方法綁定到模闆底部的 Back(後退)按鈕上。

&lt;button (click)="goBack()"&gt;Back&lt;/button&gt;

當使用者從儀表盤中選擇了一位英雄時,本應用要導航到<code>GeneralDetailComponent</code>以檢視和編輯所選的英雄。

雖然儀表盤英雄被顯示為像按鈕一樣的方塊,但是它們的行為應該像錨标簽一樣。 當滑鼠移動到一個英雄方塊上時,目标 URL 應該顯示在浏覽器的狀态條上,使用者應該能拷貝連結或者在新的浏覽器标簽頁中打開英雄詳情視圖。

要達到這個效果,再次打開<code>dashboard.component.html</code>,将用來疊代的<code>&lt;div *ngFor...&gt;</code>替換為<code>&lt;a&gt;</code>,就像這樣:

重新整理浏覽器,并從儀表盤中選擇一位英雄,應用就會直接導航到英雄的詳情。

重構路由為一個子產品

<code>AppModule</code>中有将近 20 行代碼是用來配置四個路由的。 絕大多數應用有更多路由,并且它們還有守衛服務來保護不希望或未授權的導航。  路由的配置可能迅速占領這個子產品,并掩蓋其主要目的,即為 Angular 編譯器設定整個應用的關鍵配置。

按約定,路由子產品的名字應該包含 “Routing”,并與導航到的元件所在的子產品的名稱看齊。

在<code>app.module.ts</code>所在目錄建立<code>app-routing.module.ts</code>檔案。将下面從<code>AppModule</code>類提取出來的代碼拷貝進去:

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

import { RouterModule, Routes } from '@angular/router';

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

const routes: Routes = [

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

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

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

  { path: 'generals',     component: GeneralsComponent }

];

  imports: [ RouterModule.forRoot(routes) ],

  exports: [ RouterModule ]

export class AppRoutingModule {}

将路由抽出到一個變量中。如果你将來要導出這個子產品,這種 "路由子產品" 的模式也會更加明确。

添加<code>RouterModule.forRoot(routes)</code>到<code>imports</code>。

無<code>declarations</code>!聲明是關聯子產品的任務。

如果有守衛服務,把它們添加到本子產品的<code>providers</code>中(本例子中沒有守衛服務)。

删除<code>AppModule</code>中的路由配置,并導入<code>AppRoutingModule</code> (使用 ES <code>import</code>語句導入,并将它添加到<code>NgModule.imports</code>清單)。

//路由所需要的核心子產品

import { RouterModule }   from '@angular/router';

//路由子產品

import {AppRoutingModule} from './app-routing.module'

      DashboardComponent

    FormsModule,

   //路由子產品

    AppRoutingModule

  在&lt;div class="controller"&gt;  添加如下代碼 

    &lt;div class="row"&gt;

     &lt;div *ngIf="selectGeneral"&gt;

       &lt;h2&gt;

         {{selectGeneral.name | uppercase}} is my hero

       &lt;/h2&gt;

       &lt;button (click)="gotoDetail()"&gt;View Details&lt;/button&gt;

     &lt;/div&gt;

     &lt;/div&gt;

點選按鈕時,GeneralsComponent導航到<code>GeneralDetailComponent</code>。 該按鈕的點選事件綁定到了<code>gotoDetail()</code>方法,它使用指令式的導航,告訴路由器去哪兒。

該方法需要對元件類做一些修改:

實作<code>gotoDetail()</code>,調用路由器的<code>navigate()</code>方法

具體代碼

import { Router } from '@angular/router';

   constructor(private generalService:GeneralService,private router: Router){

  gotoDetail(): void {

    this.router.navigate(['/detail', this.selectGeneral.id]);

重新整理浏覽器,并開始點選。 我們能在應用中導航:從儀表盤到英雄詳情再回來,從英雄清單到 mini 版英雄詳情到英雄詳情,再回到英雄清單。 我們可以在儀表盤和英雄清單之間跳來跳去。

本文轉自 沉迷學習中 51CTO部落格,原文連結:http://blog.51cto.com/12907581/1967223,如需轉載請自行聯系原作者