過去在 Angular Router 中,如果希望進入 Component 前先完成資料載入,可以透過 Resolver,讓資料取得流程在 Component 初始化之前執行。
而即將登場的 Route Resources,則把 Angular 的 Resource 機制整合進 Router。這篇會先從傳統 Resolver 開始,看看 Route Resources 在路由資料載入上帶來哪些不同的處理方式。
目前 Route Resources 仍處於 Developer Preview,API 與行為後續仍可能調整。使用前需要先透過 withRouterResources() 啟用;如果希望 Router 將 Resource 綁定到 Component Input,也可以一起加入 withComponentInputBinding():
import { ApplicationConfig } from '@angular/core';
import {
provideRouter,
withComponentInputBinding,
withRouterResources,
} from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(
routes,
withComponentInputBinding(),
withRouterResources()
),
],
};
當路由存在巢狀結構,而且 Parent Route 與 Child Route 都需要透過 Resolver 取得資料時,Resolver 會依照路由層級執行。
假設路由結構如下:
/users/:id
└── posts
進入 /users/1/posts 時:
使用 Resolver 可以這樣設定:
export const routes: Routes = [
{
path: 'users/:id',
resolve: {
user: userResolver,
},
children: [
{
path: 'posts',
component: UserPostsComponent,
resolve: {
posts: postsResolver,
},
},
],
},
];
Router 會先執行 Parent Route 的 userResolver,等資料取得完成後,才繼續執行 Child Route 的 postsResolver。
假設兩個 API 分別需要:
User API 300ms
Posts API 400ms
由於兩個 Resolver 依序執行,整體等待時間大約會是:
300ms + 400ms = 700ms
如果 Posts 的查詢必須依賴 User 的結果,依序執行本身沒有問題。這類情境也可以回頭檢視 API 設計,確認是否真的需要拆成兩段請求,或能否由後端一次提供需要的資料。
如果兩個請求彼此獨立,卻只是因為 Parent / Child 的路由結構而被安排成前後執行,就會形成不必要的瀑布流。
同樣的情境改用 Route Resources,可以把 Parent 與 Child 的資料載入分別宣告成各自的 Resource:
export const routes: Routes = [
{
path: 'users/:id',
resources: (ctx) => ({
user: resource({
params: () => ctx.params()['id'],
loader: ({ params: id, abortSignal }) =>
fetchUser(id, abortSignal),
}),
}),
children: [
{
path: 'posts',
component: UserPostsComponent,
resources: (ctx) => ({
posts: resource({
params: () => ctx.params()['id'],
loader: ({ params: id, abortSignal }) =>
fetchPosts(id, abortSignal),
}),
}),
},
],
},
];
ResourceContext 提供 params、queryParams、fragment 與 data 等 Route 資訊,而且這些資料都是 Signal。
因此可以直接把 Route Param 當成 Resource 的 params:
params: () => ctx.params()['id']
以前面的路由為例,Child Route 的 posts Resource 也能取得 Parent Route 定義的 id:
/users/:id
└── posts
resources: (ctx) => ({
posts: resource({
params: () => ctx.params()['id'],
loader: ({ params: id }) =>
fetchPosts(id),
}),
}),
目前 Router 預設的 paramsInheritanceStrategy 是 always,因此 Child Route 會繼承 Parent Route 的 Params,ctx.params()['id'] 可以直接取得 id。
在 Component 中透過 ActivatedRoute 讀取 Route Param 也可以做到:
const route = inject(ActivatedRoute);
route.paramMap.subscribe((params) => {
console.log(params.get('id'));
});
差別在於 Route Resources 可以把 Route Param 與資料載入一起宣告在 Route 設定中,不需要讓 Component 自己訂閱參數後再發出請求。
當網址從:
/users/1/posts
切換成:
/users/2/posts
ctx.params()['id'] 會從 1 變成 2,兩個 Resource 的 params 也會跟著改變,重新執行對應的 loader:
fetchUser('2');
fetchPosts('2');
傳統 Resolver 在 Route Param 改變時同樣可以重新執行,因此差異不在於能不能重新取得資料,而是 Route Resources 可以直接把 Route Params 納入 Resource 的響應式資料載入流程。
另一個差異是 Parent 與 Child 的載入順序。對目前符合的 Route,Angular 會讓各層的 Resource 同時開始載入,不需要等待 Parent Resource 完成後才執行下一層。
因此相同的兩個請求,等待時間會接近其中較慢的一筆:
max(300ms, 400ms) = 400ms

這裡改善的不是 API 本身的速度,而是 Router 不再因為 Parent / Child 的層級,把原本可以平行執行的請求拆成前後兩段,對彼此獨立的資料來說,就能減少因路由結構產生的額外等待時間。
Route Resources 預設是 Blocking,Router 會等 Resource 載入完成後,才啟用對應的 Route 與 Component。
對 Blocking Resource 來說,Router 綁定到 Component Input 的是 Resource 完成後的 Value,因此進入 Component 時資料已經準備完成:
@Component({
selector: 'app-user-posts',
template: `
@for (post of posts(); track post.id) {
<p>{{ post.title }}</p>
}
`,
})
export class UserPostsComponent {
posts = input.required<Post[]>();
}
這種方式和 Resolver 比較接近,Component 不需要另外處理初始 Loading 狀態。
如果資料不是進入頁面的必要條件,也可以透過 nonBlocking() 讓 Route 先啟用,再讓 Resource 在背景繼續載入:
import {
nonBlocking,
Routes,
} from '@angular/router';
export const routes: Routes = [
{
path: 'users/:id',
children: [
{
path: 'posts',
component: UserPostsComponent,
resources: (ctx) => ({
posts: nonBlocking(
resource({
params: () => ctx.params()['id'],
loader: ({ params: id }) =>
fetchPosts(id),
})
),
}),
},
],
},
];
使用 nonBlocking() 後,Router 綁定到 Component Input 的不再只是 Value,而是完整的 Resource,因此可以直接取得 isLoading()、error()、hasValue() 等狀態:
@Component({
selector: 'app-user-posts',
template: `
@if (posts().isLoading()) {
<p>Loading...</p>
} @else if (posts().error()) {
<p>載入失敗</p>
} @else if (posts().hasValue()) {
@for (post of posts().value(); track post.id) {
<p>{{ post.title }}</p>
}
}
`,
})
export class UserPostsComponent {
posts = input.required<Resource<Post[]>>();
}
如果只是希望 Component 顯示後再取得資料,直接在 Component 中建立 Resource 也可以做到。
nonBlocking() 的差別在於 Resource 仍然宣告在 Route 設定中,可以繼續使用 ResourceContext 提供的 Route 資訊:
params: () => ctx.params()['id']
nonBlocking() 只改變 Router 是否要等待 Resource 完成才啟用 Route,Resource 的參數與資料取得邏輯仍然可以留在 Route 設定中,Component 只需要處理 Loading、Error 與資料顯示。

Resolver 和 Navigation 綁在一起。如果希望在同一個 URL 下重新執行 Resolver,通常需要再次觸發 Router Navigation。
Route Resources 則可以直接重新載入其中一個 Resource,不需要重新比對 Route 或重新執行 Guards。
對 Blocking Resource 來說,Component Input 收到的是 Resource 的 Value。如果需要操作 Resource 本身,可以從 ActivatedRoute.resources 取得:
import {
Component,
inject,
input,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-user-posts',
template: `
<button
type="button"
(click)="refresh()"
>
重新整理
</button>
@for (post of posts(); track post.id) {
<p>{{ post.title }}</p>
}
`,
})
export class UserPostsComponent {
posts = input.required<Post[]>();
private readonly route = inject(ActivatedRoute);
private readonly postsResource =
this.route.resources?.['posts'];
refresh() {
this.postsResource?.reload();
}
}
呼叫:
this.postsResource?.reload();
只會重新執行這一份 Resource 的載入,不需要改變目前 URL,也不會重新執行 Guards 或重新比對 Route。
如果畫面只是需要重新取得最新 Posts,就不必為了重新執行資料請求,再跑一次完整的 Navigation。

Route Resources 不限定只能使用 resource()。
resources 可以回傳 Angular 的 Resource,因此也能依照資料來源選擇不同的 Resource API。
例如使用 httpResource():
resources: (ctx) => ({
user: httpResource<User>(
() => `/api/users/${ctx.params()['id']}`
),
}),
或是使用 rxResource() 串接 Observable:
resources: (ctx) => ({
user: rxResource({
params: () => ctx.params()['id'],
stream: ({ params: id }) =>
userService.getUser(id),
}),
}),
Route Resources 並不是另一套資料請求方式,而是讓 Angular Resource 可以直接放進 Router 的資料載入流程。
原本 Resource 的響應式參數、重新載入能力,以及 nonBlocking 時可以使用的載入狀態,都能繼續沿用,再搭配 Router 提供的 Route 資訊與 Navigation 生命週期。

Resolver 的做法是先完成資料載入,再進入對應的 Route。當 Parent 與 Child 都有 Resolver 時,也會依照路由層級執行,因此彼此獨立的請求仍可能形成瀑布流。
Route Resources 把 Resource 納入 Router 後,各層 Route 的 Resource 可以平行載入,Route Params 也能直接成為 Resource 的響應式依賴。資料不需要阻塞頁面時,可以改成 nonBlocking;需要重新取得資料時,也能只重新載入特定 Resource,不必再觸發一次 Navigation。
資料是否適合放進 Route Resources,可以看它和 Route 的關係。明確依賴 Route Params、會隨網址改變,或本身就是這條 Route 所需要的資料,可以由 Router 管理;如果只是單一 Component 內部使用的資料,直接留在 Component 中會更單純。
withRouterResources()、resources 與預設 Blocking 的行為。ctx 提供的 params、queryParams、fragment、data。paramsInheritanceStrategy 決定 Child Route 是否繼承 Parent Route 的參數,預設是 always。nonBlocking() 把 Resource 標記成不阻擋導航。resources 與 ResourceContext 型別定義。withRouterResources(),目前標示為 Developer Preview。