模組化建築外牆,象徵 Angular Router 由根路由與功能模組路由組合而成

← INSIGHTS & PERSPECTIVES | 前端開發

Angular Router 進階教學:RouterModule、子路由、參數與 CanActivate

整理 Angular Router 進階用法,包含 RouterModule 拆分、forRoot 與 forChild、路由順序、程式導頁、參數讀取、路由動畫、子路由與 CanActivate。

Angular Router 進階用法的重點,是把根路由、功能模組路由、參數讀取、導頁邏輯與網址權限分層管理。當 Angular 應用程式路由變多,建議用 `AppRoutingModule` 管理全站入口,用 feature routing module 管理各功能自己的 URL,並注意萬用路由 `` 的載入順序。

Angular Router Module 應該怎麼拆?

Angular Router Module 適合在路由設定開始變複雜時拆出來。根路由使用 `RouterModule.forRoot()`,功能模組路由使用 `RouterModule.forChild()`,兩者合併後形成完整路由表。

首先,建立一個路由模組 `app-routing.module.ts`:

```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 {}

```

接著,更新 `app.module.ts`:

```ts

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 {}

```

`enableTracing: true` 只適合除錯,因為 Angular Router 會把每個 navigation event 印到 console。正式環境通常會關掉這個設定(Angular Docs,2026 年存取)。

多層 Router Module 怎麼設計?

多層 Router Module 的核心做法,是讓每個功能模組維護自己的路由。這樣 `HeroesModule` 可以同時管理英雄列表與英雄詳細頁,不必把所有 URL 都塞進根路由。

先建立功能模組 `heroes.module.ts`:

```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`:

```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 {}

```

建議把 routing module 放在伴隨的 feature module 旁邊。`heroes-routing.module.ts` 與 `heroes.module.ts` 都放在 `src/app/heroes`,之後路由變多時比較容易維護。

最後在 `heroes.module.ts` 匯入 `HeroRoutingModule`:

```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';

import { HeroRoutingModule } from './heroes-routing.module';

@NgModule({

imports: [

CommonModule,

FormsModule,

HeroRoutingModule,

],

declarations: [

HeroListComponent,

HeroDetailComponent,

],

providers: [HeroService],

})

export class HeroesModule {}

```

路由設定重複時要怎麼整理?

Angular Router 會合併根模組與功能模組匯入的路由設定。若 `path: 'heroes'` 已經移到 `HeroesRoutingModule`,根路由就應移除同一條設定,避免同一個 URL 分散在兩個地方。

整理後的 `app-routing.module.ts` 可以留下全站層級路由:

```ts

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

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

import { CrisisListComponent } from './crisis-list.component';

import { PageNotFoundComponent } from './not-found.component';

const appRoutes: Routes = [

{ path: 'crisis-center', component: CrisisListComponent },

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

{ path: '', component: PageNotFoundComponent },

];

@NgModule({

imports: [

RouterModule.forRoot(

appRoutes,

{ enableTracing: true } // debugging purposes only

),

],

exports: [

RouterModule,

],

})

export class AppRoutingModule {}

```

`app.module.ts` 則先匯入 `HeroesModule`,再匯入 `AppRoutingModule`:

```ts

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 {}

```

這裡的資訊增益是實務判斷:把 feature route 放在 feature module,根路由只保留首頁 redirect、跨功能頁面與 `PageNotFoundComponent`。路由責任邊界清楚後,日後新增 lazy loading 或權限保護會比較好改。

Angular Router 的載入順序為什麼重要?

Angular Router 會依照路由表順序比對 URL,萬用路由 `` 必須放在最後。若 `AppRoutingModule` 先被載入,而且 `` 早於 feature route,合法的功能頁面也可能被導到 `PageNotFoundComponent`。

當所有路由都在 `AppRoutingModule` 時,`path: ''` 要放在 `/heroes` 後面:

```ts

{ path: '', component: PageNotFoundComponent }

```

如果 routing 分散在兩個模組內,也要先載入 `HeroesModule`,再載入 `AppRoutingModule`。這樣 Angular Router 會先看見 `HeroesModule` 裡的路由設定,不會讓 `AppRoutingModule` 的萬用路由先攔截成功。

情境建議順序原因
根路由含 ``,功能模組含 `/heroes``HeroesModule` 在前,`AppRoutingModule` 在後先註冊功能路由,再讓 `` 處理找不到的頁面
所有路由都在同一個陣列具體路由在前,`` 在最後Angular Router 依序比對,先命中的路由會被使用
路由需要除錯暫時開 `enableTracing`可觀察 navigation events,但不適合長期留在正式環境

如何在程式裡操作 Angular Router 導頁?

Angular Router 可以用 `router.navigate()` 在 TypeScript 程式內導頁。陣列裡的每個項目會組成 URL 片段,也能傳入 route parameter 或 matrix parameter。

導到英雄列表:

```ts

gotoHeroes() {

this.router.navigate(['/heroes']);

}

```

帶一個 route parameter:

```ts

gotoHeroes(hero: Hero) {

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

}

```

帶 matrix parameter:

```ts

gotoHeroes(hero: Hero) {

this.router.navigate(['/hero', { id: hero.id, foo: 'foo' }]);

}

```

點擊後可能出現這樣的連結:

```console

localhost:3000/heroes;id=15;foo=foo

```

Matrix URL 使用分號 `;` 分隔參數,不是查詢字串常見的 `&`。如果資料代表某個路由片段的狀態,matrix parameter 會比 query string 更貼近 Angular Router 的語意。

如何取得 Angular Router 的參數?

Angular Router 參數通常透過 `ActivatedRoute` 讀取。`paramMap` 是 Observable,因此同一個元件被重用時,參數變動仍可被接住,不必只依賴 `ngOnInit` 的第一次執行。

我當時的筆記範例使用 `paramMap` 取得 `id` 後讀取資料:

```ts

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();

});

}

}

```

若使用較新的 RxJS 寫法,通常會把 `switchMap` 放進 `pipe()`:

```ts

import { switchMap } from 'rxjs/operators';

ngOnInit() {

this.heroes$ = this.route.paramMap.pipe(

switchMap((params: ParamMap) => {

this.selectedId = Number(params.get('id'));

return this.service.getHeroes();

})

);

}

```

Angular Router 怎麼加入換頁動畫?

Angular Router 換頁動畫需要先載入 `BrowserAnimationsModule`,再用 Angular animations 定義 route transition。常見做法是在路由目標 component 上用 `@HostBinding` 綁定動畫 trigger。

先在根模組載入動畫模組:

```ts

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

@NgModule({

imports: [

BrowserAnimationsModule,

],

})

export class AppModule {}

```

在根目錄 `src/app/` 建立 `animations.ts`:

```ts

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` 增加動畫綁定:

```ts

@HostBinding('@routeAnimation') routeAnimation = true;

@HostBinding('style.display') display = 'block';

@HostBinding('style.position') position = 'absolute';

```

Child routing component 適合解決什麼問題?

Child routing component 適合用來保留父層畫面狀態。Angular Router 預設會重用同一個 component instance,但若導頁流程讓詳細頁被銷毀,再進入下一筆資料時仍會重新建立 component。

原本的 heroes routes 是列表頁與詳細頁並列:

```ts

const heroesRoutes: Routes = [

{ path: 'heroes', component: HeroListComponent },

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

];

```

這種設計下,從 `HeroDetailComponent` 回到 `HeroListComponent` 時,詳細頁元件會被刪除。下一次選擇不同英雄時,Angular 會重新建立新的 `HeroDetailComponent`。

若想保留父層頁面狀態,可以改用子路由:

```ts

const crisisCenterRoutes: Routes = [

{

path: 'crisis-center',

component: CrisisCenterComponent,

children: [

{

path: '',

component: CrisisListComponent,

children: [

{

path: ':id',

component: CrisisDetailComponent,

},

{

path: '',

component: CrisisCenterHomeComponent,

},

],

},

],

},

];

```

`CrisisDetailComponent` 與 `CrisisCenterHomeComponent` 都是 `CrisisListComponent` 的子組件,因此使用者瀏覽不同 detail 時,父層列表狀態可以被保留。直到頁面切換離開 `CrisisCenterComponent`,Angular Router 才會銷毀這一層元件。

Angular Router 相對路徑怎麼寫?

Angular Router 的相對路徑用 `./` 表示目前位置,用 `../` 表示上一層。程式導頁時搭配 `relativeTo: this.route`,可以避免在巢狀路由裡硬寫完整絕對路徑。

回到上一層 crises 路由的範例:

```ts

// Relative navigation back to the crises

this.router.navigate(['../', { id: crisisId, foo: 'foo' }], { relativeTo: this.route });

```

巢狀路由越深,相對路徑越有價值。只要父層 path 調整,使用 `relativeTo` 的局部導頁通常不需要全部跟著改。

CanActivate 如何做網址認證?

Angular Router 的 `CanActivate` guard 可以在進入某個 URL 前先做判斷。常見用途是登入檢查、角色權限檢查,或避免未授權使用者進入管理頁。

`src/app/auth-guard.service.ts` 範例:

```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` 範例:

```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 {}

```

上面的 `canActivate()` 回傳 `true` 只是教學範例。實務上通常會檢查登入狀態,必要時回傳 `UrlTree` 導到登入頁,或回傳 Observable / Promise 等待權限資料完成。

延伸閱讀

常見問題

QAngular Router forRoot 和 forChild 差在哪?

`RouterModule.forRoot()` 用在根模組,負責建立全站 Router service 與根路由設定。`RouterModule.forChild()` 用在功能模組,只提供該功能自己的路由設定,不應重複建立 Router service。

QAngular Router 的 `path: ''` 為什麼要放最後?

`path: ''` 是萬用路由,會吃掉所有尚未匹配的 URL。若萬用路由放太前面,後面的正常路由沒有機會被比對,使用者會被錯誤導到 404 頁。

QAngular Router 可以在程式碼裡導頁嗎?

可以。注入 `Router` 後呼叫 `this.router.navigate(['/heroes'])`,即可在 TypeScript 程式碼中切換頁面;若要依目前路由導向上一層,可搭配 `relativeTo: this.route`。

QAngular Router 怎麼取得 URL 裡的 id?

常見做法是注入 `ActivatedRoute`,再訂閱 `paramMap` 或 `params`。使用 `paramMap` 的好處是同一個 component 被重用時,URL 參數變化仍會觸發資料更新。

QAngular Router 的 Matrix URL 是什麼?

Matrix URL 是把參數放在路由片段上的寫法,例如 `/heroes;id=15;foo=foo`。這種參數用分號分隔,語意上比較像某段 route 的狀態,不同於整個 URL 共用的 query string。

QAngular Router 可以用來做登入權限嗎?

可以。`CanActivate` guard 能在進入路由前判斷使用者是否登入或具備權限;若條件不符合,可以拒絕導航或導向登入頁。

參考資料

最後更新

本文最後更新於 2026-08-28。我當時的筆記發布於 2018-01-10,本文保留 Angular Router 進階範例與當時的程式碼脈絡,並補上現行站內格式、GEO 結構、FAQ 與官方參考資料。

關於作者 {#author}

Claire Chang | 企業 AI 導入與流程轉型顧問。專注於 AI Agent 架構設計、ERP 系統整合與企業 AI 治理。

首次發布:2018-01-10