首页
/ Angular 路由状态读取指南:用 ActivatedRoute、路由快照与活动路由检测构建上下文感知组件

Angular 路由状态读取指南:用 ActivatedRoute、路由快照与活动路由检测构建上下文感知组件

2026-09-06 18:43:38作者:宣聪麟

导读: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
  • childrenparentrootfirstChildpathFromRoot:在嵌套路由树中游走的引用;
  • outletrouteConfigcomponenttitle:当前出口名、对应 Route 配置与组件等元数据。

为什么推荐用 paramMap 而非直接订阅 params

参数对象本身的取值是无类型约定的 params['id']。相比之下,ActivatedRoute.paramMap 暴露的是 ParamMap 接口(含 gethasgetAll 等方法),取值方式统一、且能正确处理同一参数名的多个值。订阅流的语义一致,只是读取 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: Dataparams: ParamsqueryParams: ParamsparamMapfragment: string | nulltitle: string | undefined 等都是直接取值而非 Observable,同时保留了 childrenparentrootfirstChildpathFromRootoutletrouteConfigcomponent 等结构信息。

实践建议:如果组件只在创建时读取一次参数(例如从 /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.navigatequeryParams

// 单个参数结构
// 结果 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 实现中被命名为 subsetMatchOptionspaths: '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 也可以放在祖先元素上,从而按你的设计自由地为任意元素加样式。此时该元素内部的多个 RouterLinkContentChildren 查询统一收集、任一命中即激活祖先元素(见 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,再用 computedcontainsTree 对「最近一次成功导航的最终 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.mdActivatedRoute / ActivatedRouteSnapshot 的完整公开属性见 goldens/public-api/router/index.api.md,而 RouterLinkActiveisActive 与匹配选项的底层实现在 packages/router/src/directives/router_link_active.tspackages/router/src/url_tree.ts

登录后查看全文
热门项目推荐
相关项目推荐