Angular 路由状态读取指南:用 ActivatedRoute、路由快照与活动路由检测构建上下文感知组件
导读:Angular Router 将「当前访问了哪条路由、携带了哪些参数」建模为一套可读的路由状态。本文基于 Angular 仓库中官方路由指南 read-route-state.md 展开,系统讲解如何用
ActivatedRoute订阅与快照两种方式读取路由信息,如何区分路由参数、查询参数与矩阵参数并写入 URL,以及如何借助RouterLinkActive指令和isActive函数检测当前激活路由。读完你将在不依赖 Router 内部细节的前提下,写出能随 URL 变化实时响应、且对无障碍友好的导航与内容组件。
Angular Router 允许你读取并与路由关联的信息进行交互,从而构建响应式、上下文感知(context-aware)的组件。下文依次展开:先看核心服务 ActivatedRoute,再看「随时间变化」与「某一时刻静态」两种读取范式,最后落到三种 URL 参数与两种「路由是否激活」的判定工具上。
用 ActivatedRoute 获取当前路由的信息
ActivatedRoute 是 @angular/router 提供的一个服务,聚合了与当前激活路由相关的全部信息。在组件中通过依赖注入即可拿到实例:
import {Component} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
@Component({
selector: 'app-product',
})
export class Product {
private activatedRoute = inject(ActivatedRoute);
constructor() {
console.log(this.activatedRoute);
}
}
官方指南重点列举了四个常用属性:
| 属性 | 说明 |
|---|---|
url |
一个 Observable,包含路由路径,按路径每一段表示为字符串数组(UrlSegment[])。 |
data |
一个 Observable,包含为路由提供的 data 对象;同时包含 resolve 守卫解析出的值。 |
params |
一个 Observable,包含该路由专属的必选参数与可选参数。 |
queryParams |
一个 Observable,包含对所有路由都可见的查询参数。 |
从仓库的 API 面(goldens/public-api/router/index.api.md)可以看到 ActivatedRoute 实际暴露的属性远不止这四项,还包括:
snapshot:当前时刻的ActivatedRouteSnapshot(见下一节);paramMap/queryParamMap:对参数做类型安全访问的ParamMap(通过get/has/getAll取值);fragment:URL 片段(#...部分)的Observable;children、parent、root、firstChild、pathFromRoot:在嵌套路由树中游走的引用;outlet、routeConfig、component、title:当前出口名、对应Route配置与组件等元数据。
为什么推荐用 paramMap 而非直接订阅 params
参数对象本身的取值是无类型约定的 params['id']。相比之下,ActivatedRoute.paramMap 暴露的是 ParamMap 接口(含 get、has、getAll 等方法),取值方式统一、且能正确处理同一参数名的多个值。订阅流的语义一致,只是读取 API 更规范:
this.route.paramMap.subscribe((paramMap) => {
const id = paramMap.get('id'); // string | null
});
理解路由快照(Route Snapshot)
页面导航是一个随时间推进的事件过程,而“在某一给定时刻的路由状态”可以通过路由快照获取。
路由快照包含该路由的关键信息,包括参数、数据和子路由;关键在于快照是静态的,不会反映未来的变化。
import {ActivatedRoute, ActivatedRouteSnapshot} from '@angular/router';
@Component(/* ... */)
export class UserProfile {
readonly userId: string;
private route = inject(ActivatedRoute);
constructor() {
// 示例 URL: https://www.angular.dev/users/123?role=admin&status=active#contact
// 从快照读取路由参数
this.userId = this.route.snapshot.paramMap.get('id');
// 一次性读取多个路由元素
const snapshot = this.route.snapshot;
console.log({
url: snapshot.url, // https://www.angular.dev
// 路由参数对象: {id: '123'}
params: snapshot.params,
// 查询参数对象: {role: 'admin', status: 'active'}
queryParams: snapshot.queryParams,
});
}
}
对照仓库 API golden(goldens/public-api/router/index.api.md 第 59–80 行)可以看出,ActivatedRouteSnapshot 基本是 ActivatedRoute 的“非流式镜像”:url: UrlSegment[]、data: Data、params: Params、queryParams: Params、paramMap、fragment: string | null、title: string | undefined 等都是直接取值而非 Observable,同时保留了 children、parent、root、firstChild、pathFromRoot、outlet、routeConfig、component 等结构信息。
实践建议:如果组件只在创建时读取一次参数(例如从 /users/123 直接进入详情页),快照完全够用;但若组件需要响应同一路由实例上的参数变化(例如从 /users/123 导航到 /users/456 且复用组件实例),应订阅 params / paramMap,否则读到的永远是首次激活时的旧值。这正是官方文档提醒“快照是静态、不会反映未来变化”的典型场景。
读取路由上的参数
开发者可以从一条路由上利用两类参数:路由参数(route parameters)与查询参数(query parameters),此外还有作用域更细的矩阵参数(matrix parameters)。
路由参数(Route Parameters)
路由参数允许你通过 URL 向组件传递数据,适合根据 URL 中的标识符(如用户 ID、产品 ID)展示特定内容。
定义路由参数只需在 path 中给参数名加冒号(:)前缀,具体约定见 定义路由指南中的 “Define URL Paths with Route Parameters” 一节:
import {Routes} from '@angular/router';
import {Product} from './product';
const routes: Routes = [{path: 'product/:id', component: Product}];
随后通过订阅 route.params 访问参数值:
import {Component, inject, signal} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
@Component({
selector: 'app-product-detail',
template: `<h1>Product Details: {{ productId() }}</h1>`,
})
export class ProductDetail {
productId = signal('');
private activatedRoute = inject(ActivatedRoute);
constructor() {
// 订阅方式访问路由参数,可响应同一组件实例上的参数变化
this.activatedRoute.params.subscribe((params) => {
this.productId.set(params['id']);
});
}
}
查询参数(Query Parameters)
查询参数为通过 URL 传递可选数据提供了灵活方式,且不改变路由结构。与路由参数不同,查询参数可以跨多次导航持续存在,非常适合过滤、排序、分页等“带状态的 UI 元素”。
写入查询参数使用 Router.navigate 的 queryParams:
// 单个参数结构
// 结果 URL: /products?category=electronics
router.navigate(['/products'], {
queryParams: {category: 'electronics'},
});
// 多个参数
// 结果 URL: /products?category=electronics&sort=price&page=1
router.navigate(['/products'], {
queryParams: {
category: 'electronics',
sort: 'price',
page: 1,
},
});
读取查询参数使用 route.queryParams。下面是一个 ProductList 示例——排序下拉框改变时更新影响列表展示的查询参数,组件再响应式地按新参数加载数据:
import {ActivatedRoute, Router} from '@angular/router';
@Component({
selector: 'app-product-list',
template: `
<div>
<select (change)="updateSort($event)">
<option value="price">Price</option>
<option value="name">Name</option>
</select>
<!-- Products list -->
</div>
`,
})
export class ProductList {
private route = inject(ActivatedRoute);
private router = inject(Router);
constructor() {
// 响应式读取查询参数
this.route.queryParams.subscribe((params) => {
const sort = params['sort'] || 'price';
const page = Number(params['page']) || 1;
this.loadProducts(sort, page);
});
}
updateSort(event: Event) {
const sort = (event.target as HTMLSelectElement).value;
// 更新 URL 中的查询参数
this.router.navigate([], {
queryParams: {sort},
queryParamsHandling: 'merge', // 保留其他既有查询参数
});
}
}
示例中用户通过下拉框选择按名称(name)或价格(price)排序:change 处理器更新 URL 查询参数,而查询参数的变化又触发订阅回调,从而重读参数并刷新产品列表,形成一个闭环。
关于 queryParamsHandling 取值的完整语义('merge' 表示与既有查询参数合并、'preserve' 表示保留旧查询参数而丢弃新值、'' 默认表示替换),可查阅仓库中 Router 公开 API 面中与之关联的导航选项类型定义(goldens/public-api/router/index.api.md)。
矩阵参数(Matrix Parameters)
矩阵参数是归属到某个具体 URL 段的可选参数,而非作用于整条路由。查询参数跟在 ? 之后、全局生效;矩阵参数则以分号(;)形式限定在单个路径段内:
// URL 形态: /path;key=value
// 多参数形态: /path;key1=value1;key2=value2
// 携带矩阵参数导航
this.router.navigate(['/awesome-products', {view: 'grid', filter: 'new'}]);
// 结果 URL: /awesome-products;view=grid;filter=new
矩阵参数适合向特定路由段附带辅助数据,既不改变路由定义,也不影响路由匹配行为。与查询参数一样,它们无需在路由配置中预先声明。
使用 ActivatedRoute 读取矩阵参数:
import {Component, inject} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
@Component(/* ... */)
export class AwesomeProducts {
private route = inject(ActivatedRoute);
constructor() {
// 通过 params 访问矩阵参数
this.route.params.subscribe((params) => {
const view = params['view']; // 例如 'grid'
const filter = params['filter']; // 例如 'new'
});
}
}
注意:使用 withComponentInputBinding 特性时,矩阵参数(以及路由参数、查询参数)也会被绑定为组件输入,可作为 ActivatedRoute 之外的另一条访问路径。该特性在仓库中由 packages/router/src/router_config.ts 暴露,是配置应用级 Router provider 时传入的特性之一。
用 RouterLinkActive 检测当前激活路由
RouterLinkActive 指令根据当前激活路由动态地为导航元素添加样式类,常见于导航栏中,用来明确告知用户当前所在路由:
<nav>
<a
class="button"
routerLink="/about"
routerLinkActive="active-button"
ariaCurrentWhenActive="page"
>
About
</a>
|
<a
class="button"
routerLink="/settings"
routerLinkActive="active-button"
ariaCurrentWhenActive="page"
>
Settings
</a>
</nav>
当 URL 与对应 routerLink 匹配时,Angular Router 会把 active-button 类加到正确的 <a> 上,并把 ariaCurrentWhenActive 设为 page。
如果需要添加多个类,既可用空格分隔字符串,也可用数组:
<!-- 空格分隔字符串写法 -->
<a routerLink="/user/bob" routerLinkActive="class1 class2">Bob</a>
<!-- 数组写法 -->
<a routerLink="/user/bob" [routerLinkActive]="['class1', 'class2']">Bob</a>
从指令实现看,这两种写法最终会在 routerLinkActive 的 setter 中被归一化处理:数组直接使用,字符串按空格 split 后过滤空项得到类名列表(见 packages/router/src/directives/router_link_active.ts 第 208–216 行)。
aria-current 与无障碍
当你为 routerLinkActive 指定一个值时,同时也隐式定义了 ariaCurrentWhenActive 的取值。这能保证视觉受损用户(可能感知不到被应用的不同样式)也能识别出当前激活项。
若想为 aria 指定不同的值,需要用 ariaCurrentWhenActive 指令显式设置。该输入支持的取值包括 'page' | 'step' | 'location' | 'date' | 'time' | true | false(见 router_link_active.ts 第 141–148 行)。
默认的路由匹配策略
默认情况下,RouterLinkActive 把路由的任意祖先都视为匹配:
<a [routerLink]="['/user/jane']" routerLinkActive="active-link"> User </a>
<a [routerLink]="['/user/jane/role/admin']" routerLinkActive="active-link"> Role </a>
当用户访问 /user/jane/role/admin 时,两个链接都会获得 active-link 类——因为 /user/jane 是该 URL 路径的祖先段。
这一默认行为对应 RouterLinkActive 输入 routerLinkActiveOptions 的缺省值 {exact: false}(见 router_link_active.ts 第 135–139 行)。若传入 null,则无论当前 URL 如何,链接都不会被视为激活。
只在精确匹配时生效
如果希望仅精确命中才添加类,需要给 routerLinkActiveOptions 传入包含 exact: true 的配置对象:
<a
[routerLink]="['/user/jane']"
routerLinkActive="active-link"
[routerLinkActiveOptions]="{exact: true}"
>
User
</a>
<a
[routerLink]="['/user/jane/role/admin']"
routerLinkActive="active-link"
[routerLinkActiveOptions]="{exact: true}"
>
Role
</a>
需要对匹配更精细时需注意:exact: true 其实是一组完整匹配选项的语法糖:
// `exact: true` 等价于
{
paths: 'exact',
fragment: 'ignored',
matrixParams: 'ignored',
queryParams: 'exact',
}
// `exact: false` 等价于
{
paths: 'subset',
fragment: 'ignored',
matrixParams: 'ignored',
queryParams: 'subset',
}
这两种默认配置在 Router 实现中被命名为 subsetMatchOptions(paths: 'subset'、fragment: 'ignored'、matrixParams: 'ignored'、queryParams: 'subset')等常量,并经由 containsTree 做树形包含比较:路径与查询参数按比较表逐段匹配、片段与矩阵参数默认忽略(见 packages/router/src/url_tree.ts 第 95–162 行)。因此当需要更精确的控制时,routerLinkActiveOptions 也接受 IsActiveMatchOptions 的部分字段,从而自由组合 paths / queryParams / matrixParams / fragment 各自的比较方式('exact'、'subset'、'ignored')。
把 RouterLinkActive 应用到祖先元素
RouterLinkActive 也可以放在祖先元素上,从而按你的设计自由地为任意元素加样式。此时该元素内部的多个 RouterLink 由 ContentChildren 查询统一收集、任一命中即激活祖先元素(见 router_link_active.ts 第 113–114 行):
<div routerLinkActive="active-link" [routerLinkActiveOptions]="{exact: true}">
<a routerLink="/user/jim">Jim</a>
<a routerLink="/user/bob">Bob</a>
</div>
访问 /user/jim 或 /user/bob 时,外层 <div> 都会带上 active-link。
需要留意的是:RouterLinkActive 是基于 ContentChildren 的查询,无法穿透其他组件的模板——组件模板对其祖先而言是黑盒,因此跨组件模板的链接不会被收集到。
用 isActive 检查某个 URL 是否激活
isActive 函数返回一个计算信号(computed signal),用来跟踪给定 URL 当前是否在 Router 中处于激活状态;随着 Router 状态变化,该信号会自动更新。这也使它可以被放进模板的 class 绑定中,实现声明式的样式切换:
import {Component, inject} from '@angular/core';
import {isActive, Router} from '@angular/router';
@Component({
template: `
<div [class.active]="isSettingsActive()">
<h2>Settings</h2>
</div>
`,
})
export class Panel {
private router = inject(Router);
isSettingsActive = isActive('/settings', this.router, {
paths: 'subset',
queryParams: 'ignored',
fragment: 'ignored',
matrixParams: 'ignored',
});
}
其实现位于 packages/router/src/url_tree.ts 第 105–132 行:函数先把入参 URL 解析为 UrlTree,再用 computed 把 containsTree 对「最近一次成功导航的最终 URL」的比较结果包装成信号——即当前 URL 的激活状态随每次成功导航自动重算。代码注释同时给出 matchOptions 缺省字段的回退规则:
paths:'subset'queryParams:'subset'matrixParams:'ignored'fragment:'ignored'
isActive 既可接收 string 也可接收已解析的 UrlTree,并从源码注释可见其 @publicApi 标记(同一文件 117 行标注)。与 RouterLinkActive 相比,它是编程式、模板无关的判定方式,适合放在组件逻辑或计算属性里使用。
小结与取舍对照
| 场景 | 推荐方式 | 说明 |
|---|---|---|
| 进入组件时读取一次参数 | route.snapshot.paramMap.get('id') |
快照静态、同步、轻量 |
| 同一实例上参数可能变化 | route.paramMap.subscribe(...) |
每次导航都会发出新值 |
| 全局性的过滤 / 排序 / 分页 | queryParams + queryParamsHandling: 'merge' |
不破坏路由结构、可跨导航保留 |
| 只属于某 URL 段的附属数据 | 矩阵参数(;key=value) |
作用域限于单个路径段 |
| 高亮导航中的当前项 | RouterLinkActive(配合 ariaCurrentWhenActive) |
模板驱动;默认祖先也匹配,可 exact: true |
| 在逻辑/模板中程序化判断 URL 激活 | isActive(url, router, matchOptions) |
返回自动更新的 computed signal |
围绕本主题的进一步资料可在仓库对应位置展开:路由参数定义的完整规则见 adev/src/content/guide/routing/define-routes.md,ActivatedRoute / ActivatedRouteSnapshot 的完整公开属性见 goldens/public-api/router/index.api.md,而 RouterLinkActive、isActive 与匹配选项的底层实现在 packages/router/src/directives/router_link_active.ts 与 packages/router/src/url_tree.ts。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00