路由狀態管理在前端開發中比較少被單獨拿出來討論,但在 Angular 裡,一次路由導航其實會經過不少階段,也會在過程中發出不同的 Router Event,例如 NavigationStart、NavigationEnd、NavigationCancel 與 NavigationError。
過去要觀察這些狀態,通常會透過 Router.events 取得事件,再搭配 Router.getCurrentNavigation() 讀取正在執行的 Navigation。
隨著 Angular 持續導入 Signal,Router 也開始提供對應的 Signal API。這篇就從導航事件開始,看看過去怎麼處理 Router State,以及現在有哪些狀態可以直接透過 Signal 取得。
在 Angular 中,路由導航不只是把一個網址換成另一個網址。
從使用者點擊連結開始,到新的頁面完成導航,中間可能會經過路由比對、Guard 檢查、Resolver 執行、Lazy Loading 與元件啟用等階段。
Angular Router 會在這些階段發出對應的事件,讓我們知道這次 Navigation 執行到哪個位置。

一次導航通常會從 NavigationStart 開始,接著進行路由比對。如果有設定 Guard 或 Resolver,也會進入對應的檢查與資料解析流程。
最後會進入其中一種結果:
NavigationEnd:導航成功完成NavigationCancel:導航被取消NavigationError:導航過程發生錯誤NavigationSkipped:Router 判斷這次導航不需要重新執行,例如再次導航到相同 URL實際流程不一定每次都會經過所有階段,例如沒有設定 Guard 或 Resolver 時,就不會進入對應的處理流程。
如果想直接觀察 Router 發出的事件,可以在 provideRouter() 加上 withDebugTracing():
import { ApplicationConfig } from '@angular/core';
import {
provideRouter,
withDebugTracing,
} from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(
routes,
withDebugTracing()
),
],
};
啟用後,導航期間的 Router Event 都會輸出到瀏覽器 Console,方便查看實際執行順序。
withDebugTracing() 主要用於開發與除錯,正式環境通常不需要開啟。
Angular Router 提供 Router.events,可以取得 Navigation 過程中發出的事件。
import { inject } from '@angular/core';
import {
NavigationCancel,
NavigationEnd,
NavigationError,
NavigationStart,
Router,
} from '@angular/router';
export class AppComponent {
private readonly router = inject(Router);
constructor() {
this.router.events.subscribe(event => {
if (event instanceof NavigationStart) {
console.log('開始導航');
}
if (event instanceof NavigationEnd) {
console.log('導航完成');
}
if (event instanceof NavigationCancel) {
console.log('導航取消');
}
if (event instanceof NavigationError) {
console.log('導航發生錯誤');
}
});
}
}
Router.events 是一個 Observable,會隨著 Navigation 進行持續發出不同類型的 Router Event。
它適合用在需要針對特定事件處理的情境,例如頁面瀏覽紀錄、錯誤追蹤,或觀察 Guard、Resolver 的執行狀態。
如果要顯示頁面切換中的 Loading,也可以在 NavigationStart 時開啟,再於 NavigationEnd、NavigationCancel 或 NavigationError 時關閉。
不過,如果只是想知道「目前是否有 Navigation 正在進行」,就不一定要自己從多個事件整理出狀態。過去可以透過 Router.getCurrentNavigation() 直接讀取目前正在執行的 Navigation。
Router.getCurrentNavigation() 會回傳目前正在執行的 Navigation:
const navigation = this.router.getCurrentNavigation();
Navigation 進行期間會取得對應的 Navigation 物件,沒有導航正在執行時則會回傳 null。
例如可以用來判斷路由是否正在切換:
const isNavigating =
this.router.getCurrentNavigation() !== null;
不過 getCurrentNavigation() 只會讀取執行當下的值,本身不具備響應式能力。
當 Router 之後開始或完成另一個 Navigation,前面讀到的結果不會自動更新,因此過去常會搭配 Router.events,在事件發生時重新讀取:
this.router.events.pipe(
map(() => this.router.getCurrentNavigation() !== null)
);
到了 Angular 20.2,getCurrentNavigation() 已經標記為 Deprecated,改由 currentNavigation Signal 取代。
import { computed, inject } from '@angular/core';
import { Router } from '@angular/router';
export class AppComponent {
private readonly router = inject(Router);
readonly isNavigating = computed(
() => this.router.currentNavigation() !== null
);
}
currentNavigation 的型別是:
Signal<Navigation | null>
Navigation 開始後會取得這次 Navigation 的資訊,結束後則回到 null。依賴它的 computed() 也會跟著重新計算,不需要再透過 Router Event 觸發讀取。

currentNavigation() 代表的是正在執行的 Navigation。如果導航已經完成,要讀取目前所在的 URL,則可以使用 router.url。
可以直接把 currentNavigation() 的內容顯示在 Template 中:
<nav>
<a routerLink="/home">Home</a>
|
<a routerLink="/user">User</a>
</nav>
<hr />
<p>
Navigation ID:
{{ router.currentNavigation()?.id ?? '目前沒有導航' }}
</p>
<p>
前往:
{{ router.currentNavigation()?.initialUrl?.toString() ?? '-' }}
</p>
<p>
Trigger:
{{ router.currentNavigation()?.trigger ?? '-' }}
</p>
<p>
目前 URL:
{{ router.url }}
</p>
<router-outlet />
Component 中只需要取得 Router:
import { Component, inject } from '@angular/core';
import {
Router,
RouterLink,
RouterOutlet,
} from '@angular/router';
@Component({
selector: 'app-root',
imports: [
RouterLink,
RouterOutlet,
],
templateUrl: './app.component.html',
})
export class AppComponent {
readonly router = inject(Router);
}
一般 Navigation 很快就會完成,因此畫面上的資料可能只會出現很短的時間。
為了方便觀察,可以加上一個故意延遲兩秒的 Guard:
import { CanActivateFn } from '@angular/router';
export const delayGuard: CanActivateFn = async () => {
await new Promise(
resolve => setTimeout(resolve, 2000)
);
return true;
};
接著把 Guard 加到兩個路由:
const routes: Routes = [
{
path: 'home',
component: HomeComponent,
canActivate: [delayGuard],
},
{
path: 'user',
component: UserComponent,
canActivate: [delayGuard],
},
];
這樣在 /home 與 /user 之間切換時,就能比較清楚地觀察 Navigation 期間的資料。
例如從 /home 前往 /user:
Navigation ID:11
前往:/user
Trigger:imperative
目前 URL:/home
此時 Navigation 還沒有完成,所以 currentNavigation() 已經記錄這次要前往 /user,但 router.url 仍然停留在原本的 /home。
其中顯示的資訊說明如下:
id:這次 Navigation 的流水號initialUrl:Navigation 一開始要前往的 URLtrigger:Navigation 的觸發方式,例如 imperative 代表透過 routerLink 或 router.navigate() 主動導航;使用瀏覽器上一頁、下一頁時則可能是 popstate

這部分可以參考 StackBlitz 範例
等導航完成後,currentNavigation() 會回到 null,而 router.url 則更新成新的 URL:
this.router.currentNavigation();
// null
this.router.url;
// '/user'
因為 currentNavigation() 只在 Navigation 期間有值,所以也可以直接拿來判斷頁面是否正在切換:
readonly isNavigating = computed(
() => this.router.currentNavigation() !== null
);
Template 再根據這個狀態決定是否顯示 Loading:
@if (isNavigating()) {
<app-page-loading />
}
<router-outlet />
這裡的 Loading 代表的是「頁面正在切換」,適合放在 Layout 或 App Shell 層級,和頁面內某支 API 的 Loading 是不同的狀態。
除了正在進行的 Navigation,Router 也提供 lastSuccessfulNavigation,用來取得最近一次成功完成的 Navigation。
在 Angular 20 中,lastSuccessfulNavigation 還是一般的 Navigation | null;到了 Angular 21,則改成:
Signal<Navigation | null>
例如:
private readonly router = inject(Router);
readonly lastNavigation =
this.router.lastSuccessfulNavigation;
讀取時直接呼叫 Signal:
const navigation =
this.router.lastSuccessfulNavigation();
它和 currentNavigation() 關注的時間點不同:
currentNavigation():正在執行的 Navigation,結束後會回到 null
lastSuccessfulNavigation():最近一次成功完成的 Navigation,會保留到下一次成功導航完成以從 /users 前往 /orders 為例:

除了 Navigation 本身,Angular Router 也提供 Signal-based 的 isActive(),用來判斷指定 URL 是否符合目前路由。
例如側邊選單希望 /users 底下的頁面都維持 Active:
import { inject } from '@angular/core';
import { isActive, Router } from '@angular/router';
export class SidebarComponent {
private readonly router = inject(Router);
readonly isUsersActive = isActive(
'/users',
this.router
);
}
isActive() 回傳:
Signal<boolean>
Template 可以直接使用:
<a
routerLink="/users"
[class.active]="isUsersActive()"
>
使用者
</a>
沒有傳入比對選項時,isActive() 的 paths 預設是 'subset',因此 /users/1、/users/2 這類子路由也會算是 Active。
其他預設值則是:
{
paths: 'subset',
queryParams: 'subset',
matrixParams: 'ignored',
fragment: 'ignored',
}
如果要自己控制比對方式,可以傳入第三個參數:
readonly isUsersActive = isActive(
'/users',
this.router,
{
paths: 'subset',
queryParams: 'ignored',
matrixParams: 'ignored',
fragment: 'ignored',
}
);
這幾個選項分別控制 URL 不同部分的比對方式:
paths: 'subset':目前 URL 位於 /users 底下就算符合,例如 /users、/users/1、/users/1/edit;改成 'exact' 則需要 Path 完全一致queryParams: 'ignored':忽略 Query Params,因此 /users?page=1 與 /users?page=2 不會影響 Active 結果matrixParams: 'ignored':忽略 Matrix Params,例如 /users;type=admin 中的 type=admin
fragment: 'ignored':忽略 Fragment,因此 /users#profile 仍然會視為 /users
過去 Router 也有:
this.router.isActive(...)
但它回傳的是一般的 boolean,只代表執行當下的結果。
新的 isActive() 則回傳 Signal<boolean>,Router State 改變時會重新計算,從 Angular 21.1 起列為正式 API。
如果只是替導覽連結加上 Active 樣式,原本的 RouterLinkActive 就已經足夠:
<a
routerLink="/users"
routerLinkActive="active"
ariaCurrentWhenActive="page"
>
使用者
</a>
RouterLinkActive 是 Directive,適合直接在 Template 上處理 class 與 aria-current。
isActive() 則回傳 Signal,因此 Active 結果還可以在 TypeScript 中繼續參與其他狀態推導。
例如根據目前是否位於 /users 底下,決定區塊標題:
readonly sectionTitle = computed(() =>
this.isUsersActive() ? '使用者管理' : '總覽'
);
當路由位於 /users 或它的子路由時,sectionTitle() 會得到 使用者管理;切換到其他路由後則會重新計算。

單純處理導覽樣式時,使用 RouterLinkActive 會比較直接;如果 Active 結果還要參與其他程式邏輯,再使用 isActive()。
Angular Router 現在可以從「事件」與「狀態」兩個方向處理導航資訊:
Router.events:處理 NavigationStart、NavigationEnd、NavigationError 等導航事件currentNavigation():取得正在進行的 NavigationlastSuccessfulNavigation():取得最近一次成功完成的 NavigationisActive():判斷指定 URL 是否符合目前路由如果需要知道某件事情「什麼時候發生」,例如記錄頁面瀏覽、追蹤 Navigation Error,使用 Router.events 會比較適合。
如果需要的是「目前是什麼狀態」,例如頁面是否正在切換、目前是否位於 /users 底下,就可以直接從對應的 Signal API 取得。
Router 開始提供 Signal API 後,改變的不只是 API 寫法,而是部分 Router State 本身變成了可以直接追蹤的響應式狀態。
currentNavigation() 代表正在進行的 Navigation,lastSuccessfulNavigation() 保留最近一次成功完成的 Navigation,而 isActive() 則讓 URL 比對結果也能直接作為 Signal 使用。
Router.events 仍然負責事件發生的時間點;Signal API 則負責讀取 Router 當下的狀態。理解這兩者的差別後,就可以依照實際需求選擇適合的方式,而不需要把所有路由狀態都從 Event Stream 中重新整理一次。
withDebugTracing(),以及用 currentNavigation() 做 Loading 的範例。RouterLinkActive 與 isActive() 的用法。currentNavigation、lastSuccessfulNavigation 的型別,以及 getCurrentNavigation() 自 20.2 起 Deprecated。isActive() 的比對預設值與實作。RouterLinkActive 內部透過 isActive() 判斷路由狀態。Nice writeup. I just found out about isActive() two days ago.