設定Router Module
若我們的Router設定較為複雜時,可將Router
配置為一個Router Module。
首先,設定一個路由模組app-routing.module.ts
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { CrisisListComponent } from './crisis-list.component'; import { HeroListComponent } from './hero-list.component'; import { PageNotFoundComponent } from './not-found.component'; const appRoutes: Routes = [ { path: 'crisis-center', component: CrisisListComponent }, { path: 'heroes', component: HeroListComponent }, { path: '', redirectTo: '/heroes', pathMatch: 'full' }, { path: '**', component: PageNotFoundComponent } ]; @NgModule({ imports: [ RouterModule.forRoot( appRoutes, { enableTracing: true } // <-- debugging purposes only ) ], exports: [ RouterModule ] }) export class AppRoutingModule {}[/code] 接著,更新<code>app.module.ts</code>文件 [code lang="js"]import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { AppRoutingModule } from './app-routing.module'; import { CrisisListComponent } from './crisis-list.component'; import { HeroListComponent } from './hero-list.component'; import { PageNotFoundComponent } from './not-found.component'; @NgModule({ imports: [ BrowserModule, FormsModule, AppRoutingModule ], declarations: [ AppComponent, HeroListComponent, CrisisListComponent, PageNotFoundComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }
多層的Router Module
我們在做網站時,會希望每個模組專注在自己的功能上,Router也不例外。因此像英雄列表的元件,如果希望點選列表內容,可以進入該英雄詳細資料的頁面如下:
首先,創建一個pre-routing檔案heroes.module.ts
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { HeroListComponent } from './hero-list.component'; import { HeroDetailComponent } from './hero-detail.component'; import { HeroService } from './hero.service'; @NgModule({ imports: [ CommonModule, FormsModule, ], declarations: [ HeroListComponent, HeroDetailComponent ], providers: [ HeroService ] }) export class HeroesModule {}
新增一個heroes-routing.module.ts
,內容如下:
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { HeroListComponent } from './hero-list.component'; import { HeroDetailComponent } from './hero-detail.component'; const heroesRoutes: Routes = [ { path: 'heroes', component: HeroListComponent }, { path: 'hero/:id', component: HeroDetailComponent } ]; @NgModule({ imports: [ RouterModule.forChild(heroesRoutes) ], exports: [ RouterModule ] }) export class HeroRoutingModule { }
將Router Module的檔案放在與其伴隨的模組在相同的文件夾中。這兩個
heroes-routing.module.ts
和heroes.module.ts
在同一個src/app/heroes
文件夾中。考慮給每個功能模組自己的路由配置文件。當功能路線很簡單時,看起來可能會過早。但是,路線趨於變得更加複雜,模式的一致性會隨著時間的推移而得到回報。
接著在heroes.module.ts
裡導入HeroRoutingModule
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { HeroListComponent } from './hero-list.component'; import { HeroDetailComponent } from './hero-detail.component'; import { HeroService } from './hero.service'; import { HeroRoutingModule } from './heroes-routing.module'; @NgModule({ imports: [ CommonModule, FormsModule, HeroRoutingModule ], declarations: [ HeroListComponent, HeroDetailComponent ], providers: [ HeroService ] }) export class HeroesModule {}
path: 'heroes'
目前定義在兩個地方:HeroesRoutingModule和AppRoutingModule。
由功能模塊提供的route由Router合併到其導入的模塊的route中。這使您可以繼續定義功能模塊路由,而無需修改主路由配置。
如果不想設定兩次相同的資料,可以把舊的Router設定移除
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { CrisisListComponent } from './crisis-list.component'; // import { HeroListComponent } from './hero-list.component'; // <-- delete this line import { PageNotFoundComponent } from './not-found.component'; const appRoutes: Routes = [ { path: 'crisis-center', component: CrisisListComponent }, // { path: 'heroes', component: HeroListComponent }, // <-- delete this line { path: '', redirectTo: '/heroes', pathMatch: 'full' }, { path: '**', component: PageNotFoundComponent } ]; @NgModule({ imports: [ RouterModule.forRoot( appRoutes, { enableTracing: true } // <-- debugging purposes only ) ], exports: [ RouterModule ] }) export class AppRoutingModule {}[/code] 在應用程式裡面使用AppRoutingModule,打開<code>src/app/app.module.ts</code>: [code lang="js"]import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { AppRoutingModule } from './app-routing.module'; import { HeroesModule } from './heroes/heroes.module'; import { CrisisListComponent } from './crisis-list.component'; import { PageNotFoundComponent } from './not-found.component'; @NgModule({ imports: [ BrowserModule, FormsModule, HeroesModule, AppRoutingModule ], declarations: [ AppComponent, CrisisListComponent, PageNotFoundComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }
這邊要注意的是模塊導入順序很重要
當所有的路由都在AppRoutingModule時,我們將通用path: '**'
符號的設定放在路由/heroes
的後面,這樣路由器才有機會匹配一個URL到/heroes路由,而不會先被AppRoutingModule
的這個設定匹配走。
{ path: '**', component: PageNotFoundComponent }
如果Routing在兩個不同的模組內,也是要先載入HeroesModule再載入AppRoutingModule,這樣才會使用HeroesModule裡的路由設定而不會因為在AppRoutingModule找到匹配的通用符號而顯示PageNotFoundComponent。
如果將import的順序倒過來,則會顯示PageNotFoundComponent的頁面。
在程式裡去操控路由器
使用this.router.navigate
來在程式內操作路由工作
gotoHeroes() { this.router.navigate(['/heroes']); }
若需要傳一個參數,則可以用
gotoHeroes() { this.router.navigate(['/hero', hero.id]); }
要傳送兩個參數則如下
gotoHeroes(hero: Hero) { this.router.navigate(['/hero', { id: hero.id, foo: 'foo' }]); }
點下後出現的連結如下
localhost:3000/heroes;id=15;foo=foo
這個叫做Matrix URL
,在URL查詢字符串中,用分號隔開“;” 不同的參數,而不是用&
取得Router的參數
export class HeroListComponent implements OnInit { heroes$: Observable<hero[]>; private selectedId: number; constructor( private service: HeroService, private route: ActivatedRoute ) {} ngOnInit() { this.heroes$ = this.route.paramMap .switchMap((params: ParamMap) => { // (+) before `params.get()` turns the string into a number this.selectedId = +params.get('id'); return this.service.getHeroes(); }); } }
添加換頁效果
首先要載入BrowserAnimationsModule
這個動態效果模組。
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @NgModule({ imports: [ BrowserAnimationsModule
animations.ts
在根src/app/
文件夾中創建一個文件。內容如下所示:
import { animate, AnimationEntryMetadata, state, style, transition, trigger } from '@angular/core'; // Component transition animations export const slideInDownAnimation: AnimationEntryMetadata = trigger('routeAnimation', [ state('*', style({ opacity: 1, transform: 'translateX(0)' }) ), transition(':enter', [ style({ opacity: 0, transform: 'translateX(-100%)' }), animate('0.2s ease-in') ]), transition(':leave', [ animate('0.5s ease-out', style({ opacity: 0, transform: 'translateY(100%)' })) ]) ]);
然後在src/app/heroes/hero-detail.component.ts
增加使用這個動態效果的綁定
@HostBinding('@routeAnimation') routeAnimation = true; @HostBinding('style.display') display = 'block'; @HostBinding('style.position') position = 'absolute';
Child routing component
子路由元件與直接使用參數去定義路由,Router預設的狀況下,若瀏覽器重新導航到相同的元件時,會重新使用該元件既有的實體,而不會重新創建。因此在物件被重用的狀況下,該元件的ngOnInit
只會被呼叫一次,即使是要顯示不同的內容資料。
但是被創建的元件實體會在離開頁面時被銷毀並取消註冊,因此在前面範例的heroes-routing.module.ts
檔案中裡,所使用的導航設定方式,由於在瀏覽HeroDetailComponent
之後,一定要先回到HeroListComponent
,才能進入下一個Detail頁面。
const heroesRoutes: Routes = [ { path: 'heroes', component: HeroListComponent }, { path: 'hero/:id', component: HeroDetailComponent } ];
這會造成因為在回到HeroListComponent
時已把HeroDetailComponent
刪除掉了,再選擇另一個英雄要查看細節時,又會再創立一個新的HeroDetailComponent
。因此每次選擇不同的英雄時,組件都會重新創建。
如果想要保留頁面的狀態,就可以改使用子路由的方式來定義
下面是使用子路由的範例:
const crisisCenterRoutes: Routes = [ { path: 'crisis-center', component: CrisisCenterComponent, children: [ { path: '', component: CrisisListComponent, children: [ { path: ':id', component: CrisisDetailComponent }, { path: '', component: CrisisCenterHomeComponent } ] } ] } ];
因為CrisisDetailComponent
與CrisisCenterHomeComponent
都是CrisisListComponent
的子組件,因此在不同的子組件內的狀態得以被保存,直到頁面切換離開CrisisCenterComponent
時才會將元件實體刪除,這可以讓我們在瀏覽不同CrisisDetail時,得以使用到Router預設的重用設定。
使用相對路徑
./
是在目前的位置。
../
在上一層的位置。
下面為使用範例
// Relative navigation back to the crises this.router.navigate(['../', { id: crisisId, foo: 'foo' }], { relativeTo: this.route });
網址認證功能
很多時候某些頁面或許需要登入才可以檢視,有些要登入後具備某些身份才可以開啟該網址,這時候就可以用到Router的CanActivate
功能。
下面是src/app/auth-guard.service.ts
的內容:
import { Injectable } from '@angular/core'; import { CanActivate } from '@angular/router'; @Injectable() export class AuthGuard implements CanActivate { canActivate() { console.log('AuthGuard#canActivate called'); return true; } }
下面是src/app/admin/admin-routing.module.ts
的內容
import { AuthGuard } from '../auth-guard.service'; const adminRoutes: Routes = [ { path: 'admin', component: AdminComponent, canActivate: [AuthGuard], children: [ { path: '', children: [ { path: 'crises', component: ManageCrisesComponent }, { path: 'heroes', component: ManageHeroesComponent }, { path: '', component: AdminDashboardComponent } ], } ] } ]; @NgModule({ imports: [ RouterModule.forChild(adminRoutes) ], exports: [ RouterModule ] }) export class AdminRoutingModule {}
本篇所有範例可見:live demo / download example.