行動計劃
????
-
把
AppComponent
變成應用程序的“殼”,它只'處理導航' -
把現在由
AppComponent
關注的英雄們移到一個獨立的GeneralComponent
中 -
添加路由
-
創建一個新的
DashboardComponent
組件 -
把儀表盤加入導航結構中
路由是導航的另一個名字。路由器就是從一個視圖導航到另一個視圖的機制。
拆分Appcomponent.
????
????????現在的應用會加載AppComponent
組件,并且立刻顯示出英雄列表。
????????我們修改后的應用將提供一個殼,它會選擇儀表盤和英雄列表視圖之一,然后默認顯示它。
??AppComponent
組件應該只處理導航。 我們來把英雄列表的顯示職責,從AppComponent
移到GeneralComponent
組件中。
GeneralComponent組件
AppComponent
的職責已經被移交給GeneralComponent
了。 與其把AppComponent
中所有的東西都搬過去,不如索性把它改名為GeneralComponent
,然后單獨創建一個新的AppComponent
殼。
????????步驟:
????????????????1.把app.component.ts 和app.component.html 和app.component.scss移入到General文件夾下。
????????????????2.app改為generals
????????????????3.類名AppComponent改為GeneralsComponent
????????????????4.選擇器名稱app-root改為my-general
創建AppComponent
新的AppComponent
將成為應用的“殼”。 它將在頂部放一些導航鏈接,并且把我們要導航到的頁面放在下面的顯示區中。
?????????????????????在./app下創建app.component.ts
-
添加支持性的
import
語句。 -
定義一個導出的?
AppComponent
類。 -
在類的上方添加
@Component
元數據裝飾器,裝飾器帶有app-root選擇器。 -
將下面的項目從
HeroesComponent
移到AppComponent
: -
title
類屬性 -
@Component
模板中的<h1>
標簽,它包含了對title
屬性的綁定。 -
在模板的標題下面添加
<my-heroes>
標簽,以便我們仍能看到英雄列表。 -
添加
組件到根模塊的GeneralComponent
declarations
數組中,以便 Angular 能認識<my-generals>
標簽。 -
添加
GeneralService
到AppModule
的providers
數組中,因為我們的每一個視圖都需要它。 -
從
的GeneralComponent
providers
數組中移除GeneralService
,因為它被提到模塊了。 -
為
AppComponent
添加一些import
語句。
./app/app.component.ts
import { Component } from '@angular/core';
?
@Component({
? selector: 'app-root',
? template: `
? ? <my-generals></my-generals>
? `
})
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';
//注冊指令--->用法類似于組件
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的<app-root>元素標記內部。 ?
? */
? 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';
@Component({
? 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=>this.generals=generals);
? }
? ngOnInit(){
? ? ? this.getGenerals();
? }
? oSelect(item:General):void{
?? this.selectGeneral=item;
? }
}
添加路由
????????
我們希望在用戶點擊按鈕之后才顯示英雄列表,而不是自動顯示。 換句話說,我們希望用戶能“導航”到英雄列表。
我們要使用 Angular?路由器進行導航。
Angular 路由器是一個可選的外部 Angular NgModule,名叫RouterModule
。 路由器包含了多種服務(RouterModule
)、多種指令(RouterOutlet、RouterLink、RouterLinkActive
)、 和一套配置(Routes
)。我們將先配置路由。
<base href>?組件
????打開src/index.html
????????????確保它的<head>
區頂部有一個<base href="...">
元素(或動態生成該元素的腳本)
????
????配置路由 ./app/app.module.ts
????????????a.導入路由所需要的模塊
????????????????????import { RouterModule } ? from '@angular/router';
????????????b.在imports下寫入 ? ????
?????RouterModule.forRoot([
????? ? ? ? {
????? ? ? ? ? path: 'generals',
????? ? ? ? ? component: GeneralsComponent
????? ? ? ? }
????? ? ? ])
這個Routes
是一個路由定義的數組。 此時,我們只有一個路由定義,但別急,后面還會添加更多。
路由定義包括以下部分:
-
Path: 路由器會用它來匹配瀏覽器地址欄中的地址,如
generals
。 -
Component: 導航到此路由時,路由器需要創建的組件(
GeneralsComponent
)。
這里使用了forRoot()
方法,因為我們是在應用根部提供配置好的路由器。?forRoot()
方法提供了路由需要的“”路由服務提供商和指令”,并基于當前瀏覽器 URL 初始化導航。
路由出口(Outlet)
如果我們把路徑/generals
粘貼到瀏覽器的地址欄,路由器會匹配到'Generals'
路由,并顯示GeneralsComponent
組件。 我們必須告訴路由器它位置,所以我們把<router-outlet>
標簽添加到模板的底部。?RouterOutlet
是由RouterModule
提供的指令之一。 當我們在應用中導航時,路由器就把激活的組件顯示在<router-outlet>
里面。
路由器鏈接
我們當然不會真讓用戶往地址欄中粘貼路由的 URL, 而應該在模板中的什么地方添加一個錨標簽。點擊時,就會導航到GeneralsComponent
組件。
? <a routerLink="/generals">Generals</a>
注意,錨標簽中的[routerLink]
綁定。 我們把RouterLink
指令(ROUTER_DIRECTIVES
中的另一個指令)綁定到一個字符串。 它將告訴路由器,當用戶點擊這個鏈接時,應該導航到哪里。
由于這個鏈接不是動態的,我們只要用一次性綁定的方式綁定到路由的路徑 (path)?就行了。 回來看路由配置表,我們清楚的看到,這個路徑 ——?'/heroes'
就是指向HeroesComponent
的那個路由的路徑。
現在./app/app.component.ts
????
import { Component } from '@angular/core';
?
@Component({
? selector: 'app-root',
? template: `
? ?<a routerLink="/generals">Generals</a>
? ?<router-outlet></router-outlet>
? `
})
export class AppComponent {
}
AppComponent現在加上了路由器,并能顯示路由到的視圖了。 因此,為了把它從其它種類的組件中區分出來,我們稱這類組件為路由器組件。
添加一個儀表盤
當我們有多個視圖的時候,路由才有意義。所以我們需要另一個視圖。先創建一個DashboardComponent
的占位符,讓用戶可以導航到它或從它導航出來。
????在./src/app下創建dashboard文件夾
????????????在這下面創建dashboard.component.ts
import { Component } from '@angular/core';
@Component({
? selector: 'my-dashboard',
? template: '<h3>My Dashboard</h3>'
})
export class DashboardComponent { }
我們先不實現他
配置儀表盤路由
要讓app.module.ts
能導航到儀表盤,就要先導入儀表盤組件,然后把下列路由定義添加到Routes
數組中。
?{
? ? ? ? ? path: 'dashboard',
? ? ? ? ? component: DashboardComponent
? ? ? ? },
然后還得把DashboardComponent
添加到AppModule
的declarations
數組中。
? DashboardComponent
添加重定向路由
瀏覽器啟動時地址欄中的地址是/
。 當應用啟動時,它應該顯示儀表盤,并且在瀏覽器的地址欄顯示URL:/dashboard,把下列路由定義添加到
Routes
數組中。
? {
? ? ? ? ? path: '',
? ? ? ? ? redirectTo: '/dashboard',
? ? ? ? ? pathMatch: 'full'
? ? ? ? },
添加導航到模版中
在模板上添加一個到儀表盤的導航鏈接,就放在Generals(英雄列表)鏈接的上方。
? ?<a routerLink="/generals">Generals</a>
? ? <a routerLink="/dashboard">Dashboard</a>
? ?<router-outlet></router-outlet>
刷新瀏覽器。應用顯示出了儀表盤,并可以在儀表盤和英雄列表之間導航了。
把英雄添加到儀表盤
? ?????讓儀表盤變得有趣。
把元數據中的template
屬性替換為templateUrl
屬性,它將指向一個新的模板文件。
templateUrl: './dashboard.component.html'
增加styleUrls:['./dashboard.component.scss']
dashboard.component.htm ?
<h3>Top Generals</h3>
<div>
? <div *ngFor="let general of generals" class="col-1-4">
? ? <div>
? ? ? <h4>`general`.`name`</h4>
? ? </div>
? </div>
</div>
我們再次使用*ngFor
來在英雄列表上迭代,并顯示它們的名字。 還添加了一個額外的<div>
元素,來幫助稍后的美化工作。
dashboard.component.ts
import { Component,OnInit} from '@angular/core';
import {General} ?from "../../bean/General";
import {GeneralService} from'../general.service';
@Component({
? selector: 'my-dashboard',
? 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=>this.generals=generals.slice(1,5));
}
}
我們在之前的GeneralsComponent
中也看到過類似的邏輯:
-
創建一個
generals
數組屬性。 -
在構造函數中注入
GeneralService
,并且把它保存在一個私有的generalService
字段中。 -
在 Angular 的
ngOnInit
生命周期鉤子里面調用服務來獲得英雄數據。
在儀表盤中我們用Array.slice
方法提取了四個英雄(第2、3、4、5個)。
刷新瀏覽器,在這個新的儀表盤中就看到了四個英雄。
導航到英雄詳情
雖然我們在HeroesComponent
組件的底部顯示了所選英雄的詳情, 但用戶還沒法導航到GeneralDetailComponent
組件。我們可以用下列方式導航到GeneralDetailComponent
:
-
從Dashboard(儀表盤)導航到一個選定的英雄。
-
從Generals(英雄列表)導航到一個選定的英雄。
-
把一個指向該英雄的“深鏈接” URL 粘貼到瀏覽器的地址欄。
路由到一個英雄詳情
我們將在app.module.ts
中添加一個到GeneralDetailComponent
的路由,也就是配置其它路由的地方。
這個新路由的不尋常之處在于,我們必須告訴GeneralDetailComponent
該顯示哪個英雄。 之前,我們不需要告訴GeneralsComponent
組件和DashboardComponent
組件任何東西
參數化路由
我們可以把英雄的id
添加到 URL 中。當導航到一個id
為 11 的英雄時,我們期望的 URL 是這樣的:
????/detail/11
URL中的/detail/
部分是固定不變的,但結尾的數字id
部分會隨著英雄的不同而變化。 我們要把路由中可變的那部分表示成一個參數 (parameter)?或令牌 (token)?,代表英雄的id
。
配置帶參數的路由
?{
? ? ? ? ? path: 'detail/:id',
? ? ? ? ? component: GeneralDetailComponent
? ? ? ? },
路徑中的冒號 (:) 表示:id
是一個占位符,當導航到這個GeneralDetailComponent
組件時,它將被填入一個特定英雄的id
。
修改GeneralDetailComponent
模板不用修改,我們會用原來的方式顯示英雄。導致這次大修的原因是如何獲得這個英雄的數據。
我們不會再從父組件的屬性綁定中接收英雄數據。 新的GeneralDetailComponent?應該從ActivatedRoute
服務的可觀察對象params
中取得id
參數, 并通過GeneralService
服務獲取具有這個指定id
的英雄數據。
先添加下列導入語句:
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Location } ? ? ? ? ? ? ? ? from '@angular/common';
import 'rxjs/add/operator/switchMap';
import {GeneralService} from'../general.service';
然后注入ActivatedRoute
和HeroService
服務到構造函數中,將它們的值保存到私有變量中:
? constructor( private generalService: GeneralService,
? private route: ActivatedRoute,
? private location: Location){}
在ngOnInit()
生命周期鉤子中,我們從ActivatedRoute
服務的可觀察對象params
中提取id
參數, 并且使用GeneralService
來獲取具有這個id
的英雄數據。
?ngOnInit(){
?? ?this.route.paramMap
? ? .switchMap((params: ParamMap) => this.generalService.getGeneral(+params.get('id')))
? ? .subscribe(general => this.general = general);
? }
添加?GeneralService.getGeneral()
在前面的代碼片段中GeneralService沒有getGeneral()
方法。要解決這個問題,請打開GeneralService
并添加一個
方法,它會根據getGenera
()id
從
中過濾英雄列表。getGenerals
()
etGeneral(id: number): Promise<General> {
? ?return this.getGenerals()
? ? ? ? ? ? ?.then(generals => generals.find(general => general.id === id));
}
回到原路
一個goBack()
方法,它使用之前注入的Location
服務, 利用瀏覽器的歷史堆棧,導航到上一步。
?goBack(): void {
? ?this.location.back();
? }
然后,我們通過一個事件綁定把此方法綁定到模板底部的?Back(后退)按鈕上。
<button (click)="goBack()">Back</button>
選擇一個儀表盤中的英雄
當用戶從儀表盤中選擇了一位英雄時,本應用要導航到GeneralDetailComponent
以查看和編輯所選的英雄。
雖然儀表盤英雄被顯示為像按鈕一樣的方塊,但是它們的行為應該像錨標簽一樣。 當鼠標移動到一個英雄方塊上時,目標 URL 應該顯示在瀏覽器的狀態條上,用戶應該能拷貝鏈接或者在新的瀏覽器標簽頁中打開英雄詳情視圖。
要達到這個效果,再次打開dashboard.component.html
,將用來迭代的<div *ngFor...>
替換為<a>
,就像這樣:
刷新瀏覽器,并從儀表盤中選擇一位英雄,應用就會直接導航到英雄的詳情。
重構路由為一個模塊
AppModule
中有將近 20 行代碼是用來配置四個路由的。 絕大多數應用有更多路由,并且它們還有守衛服務來保護不希望或未授權的導航。 ?路由的配置可能迅速占領這個模塊,并掩蓋其主要目的,即為 Angular 編譯器設置整個應用的關鍵配置。
我們應該重構路由配置到它自己的類。 什么樣的類呢? 當前的RouterModule.forRoot()
產生一個Angular?ModuleWithProviders
,所以這個路由類應該是一種模塊類。 它應該是一個路由模塊
按約定,路由模塊的名字應該包含 “Routing”,并與導航到的組件所在的模塊的名稱看齊。
在app.module.ts
所在目錄創建app-routing.module.ts
文件。將下面從AppModule
類提取出來的代碼拷貝進去:
import { NgModule } ? ? ? ? ? ? from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
?
import {GeneralsComponent} from './general/generals.component';
import {DashboardComponent} from './dashboard/dashboard.component';
import {GeneralDetailComponent} from './general/general-detail.component';
const routes: Routes = [
? { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
? { path: 'dashboard', ?component: DashboardComponent },
? { path: 'detail/:id', component: GeneralDetailComponent },
? { path: 'generals', ? ? component: GeneralsComponent }
];
?
@NgModule({
? imports: [ RouterModule.forRoot(routes) ],
? exports: [ RouterModule ]
})
export class AppRoutingModule {}
-
將路由抽出到一個變量中。如果你將來要導出這個模塊,這種 "路由模塊" 的模式也會更加明確。
-
添加
RouterModule.forRoot(routes)
到imports
。 -
把
RouterModule
添加到路由模塊的exports
中,以便關聯模塊(比如AppModule
)中的組件可以訪問路由模塊中的聲明,比如RouterLink
?和?RouterOutlet
。 -
無
declarations
!聲明是關聯模塊的任務。 -
如果有守衛服務,把它們添加到本模塊的
providers
中(本例子中沒有守衛服務)。
修改?AppModule
刪除AppModule
中的路由配置,并導入AppRoutingModule
?(使用 ES?import
語句導入,并將它添加到NgModule.imports
列表)。
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } ? from '@angular/forms';
import {GeneralService} from'./general.service';
//路由所需要的核心模塊
import { RouterModule } ? from '@angular/router';
//路由模塊
import {AppRoutingModule} from './app-routing.module'
//注冊指令--->用法類似于組件
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 {DashboardComponent} from './dashboard/dashboard.component';
import { AppComponent } from './app.component';
import {GeneralDetailComponent} from './general/general-detail.component';
@NgModule({
? //列出程序中的組件
? declarations: [
? ? AppComponent,
? ? GeneralDetailComponent,
? ? ? GeneralsComponent,
? ? ? DashboardComponent
? ? // HighlightDirective,
? ? // HeroBirthdayComponent,
? ? // ExponentialStrengthPipe
? ],
? //導入模塊
? imports: [
? /*BrowserModule,每個運行在瀏覽器中的應用都必須導入它。
? ? ? ?BrowserModule注冊了一些關鍵的應用“服務”提供商。 它還包括了一些通用的指令,
? ? ? ?例如NgIf和NgFor,所以這些指令在該模塊的任何組件模板中都是可用的。?
? ?*/
? ? BrowserModule,
? //雙向數據綁定依賴的模塊
? ? FormsModule,
? ?//路由模塊
? ? AppRoutingModule
? ],
? providers: [GeneralService],
? /*@NgModule.bootstrap屬性把這個AppComponent標記為引導 (bootstrap) 組件。?
? ? 當 Angular 引導應用時,它會在 DOM 中渲染AppComponent,并把結果放進index.html的<app-root>元素標記內部。 ?
? */
? bootstrap: [AppComponent]
})
export class AppModule { }
在?GeneralsComponent中選擇一位英雄 ? ?
添加?mini 版英雄詳情
? 在<div class="controller"> ?添加如下代碼?
????<div class="row">
???? <div *ngIf="selectGeneral">
???? ??<h2>
???? ?? ?{{selectGeneral.name | uppercase}} is my hero
???? ??</h2>
???? ??<button (click)="gotoDetail()">View Details</button>
???? </div>
???? </div>
更新 GeneralsComponent類
點擊按鈕時,GeneralsComponent導航到GeneralDetailComponent
。 該按鈕的點擊事件綁定到了gotoDetail()
方法,它使用命令式的導航,告訴路由器去哪兒。
該方法需要對組件類做一些修改:
-
從 Angular 路由器庫導入
Router
-
在構造函數中注入
Router
(與HeroService
一起) -
實現
gotoDetail()
,調用路由器的navigate()
方法
具體代碼
import { Component,OnInit} from '@angular/core';
import { Router } from '@angular/router';
import {General} ?from "../../bean/General";
import {GeneralService} from'../general.service';
@Component({
? 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,private router: Router){
? ?}
?selectGeneral:General;
? generals:General[];
? getGenerals():void{
? ? this.generalService.getGenerals()
? ? .then(generals=>this.generals=generals);
? }
? ngOnInit(){
? ? ? this.getGenerals();
? }
? oSelect(item:General):void{
?? this.selectGeneral=item;
? }
? gotoDetail(): void {
? ? this.router.navigate(['/detail', this.selectGeneral.id]);
? }
??
}
刷新瀏覽器,并開始點擊。 我們能在應用中導航:從儀表盤到英雄詳情再回來,從英雄列表到 mini 版英雄詳情到英雄詳情,再回到英雄列表。 我們可以在儀表盤和英雄列表之間跳來跳去。