首页
/ Angular 路由导航完全指南:RouterLink 声明式导航与 Router 命令式导航实战

Angular 路由导航完全指南:RouterLink 声明式导航与 Router 命令式导航实战

2026-09-06 18:41:00作者:滑思眉Philip

本文基于 Angular 官方路由指南(navigate-to-routes.md)整理而成,系统讲解在 Angular 应用中"如何从一个路由导航到另一个路由"的两大核心手段:模板侧的 RouterLink 指令(声明式)与 TypeScript 侧的 router.navigate() / router.navigateByUrl()(命令式)。你将掌握绝对路径与相对路径的语义、字符串与数组两种命令写法、relativeTo 相对导航、browserUrl 地址栏定制等进阶能力,并能结合本仓库 packages/router 源码理解底层实现。

为什么需要 RouterLink

RouterLink 指令是 Angular 实现声明式导航的入口。它让你沿用标准锚点元素 <a> 的书写习惯,却无需刷新页面,导航行为完全交给 Angular Router 接管。

如果使用普通 <a href="...">,浏览器会发起一次全新的文档请求导致整页刷新;而添加了 routerLink 的锚点会拦截点击事件,通过 Router 内部完成无刷新的视图切换。用法上你只需在模板中书写目标路径,并确保该指令已导入组件:

import {RouterLink} from '@angular/router';

@Component({
  template: `
    <nav>
      <a routerLink="/user-profile">User profile</a>
      <a routerLink="/settings">Settings</a>
    </nav>
  `,
  imports: [RouterLink],
  ...
})
export class App {}

注意在基于 standalone 组件的写法下,RouterLink 必须显式出现在 imports 数组中。在源码中,RouterLink 是一个实现了 OnChangesOnDestroy 的指令类,定义于 router_link.ts,除 routerLink 外还暴露了 queryParamsfragmentstaterelativeToskipLocationChangereplaceUrltarget 等一组输入,本指南后续会介绍其中部分。

绝对 URL 与相对 URL

讨论 Angular 路由路径前,先厘清两种 URL 语义:

  • 绝对 URL:包含完整协议(如 https://)与根域名(如 angular.dev)的完整地址;
  • 相对 URL:省略协议与域名,仅描述应用内部路径。
<!-- 绝对 URL -->
<a href="https://www.angular.dev/essentials">Angular Essentials Guide</a>

<!-- 相对 URL -->
<a href="/essentials">Angular Essentials Guide</a>

上例中第一条完整给出了协议(https://)与根域名(angular.dev);第二条则假设用户已经位于正确的根域名下,直接指向 /essentials

一般来说更推荐相对 URL:应用部署到不同域名或子路径时无需改动,可维护性更强,因为应用内的链接不需要感知自身在完整 URL 层级中的绝对位置。

相对 URL 的两种写法:字符串与数组

Angular Router 定义导航命令(commands)有两种语法——字符串与数组:

<!-- 导航到 /dashboard -->
<a routerLink="dashboard">Dashboard</a>
<a [routerLink]="['dashboard']">Dashboard</a>

HELPFUL:字符串是最常用的相对 URL 定义方式,因为更简洁、可读。

当你需要拼接动态参数时,请改用数组语法,把动态值作为数组元素直接内插:

<a [routerLink]="['user', currentUserId]">Current User</a>

前导斜杠决定相对基准

Angular 依据相对路径是否以正斜杠 / 开头,来决定路径相对当前 URL 还是相对根域解析:

  • 不以 / 开头:相对当前 URL 解析(拼接在当前路径之后);
  • / 开头:相对根域(应用根路径)解析,属绝对式应用内路径。

例如用户当前位于 example.com/settings,不同写法的解析结果如下:

<!-- 相对当前 URL,导航到 /settings/notifications -->
<a routerLink="notifications">Notifications</a>
<!-- 相对根域,同样导航到 /settings/notifications -->
<a routerLink="/settings/notifications">Notifications</a>

<!-- 动态拼接:导航到 /team/:teamId/user/:userId -->
<a routerLink="/team/123/user/456">User 456</a>
<a [routerLink]="['/team', teamId, 'user', userId]">Current User</a>

当需要表达多个 URL 段与动态值时,数组写法与字符串写法一一对应:['/team', teamId, 'user', userId] 等价于字符串模板 /team/${teamId}/user/${userId}。在 router_link.ts 中,该输入会通过 router.createUrlTree() 转换为 UrlTree 后再触发导航(详见 router.tscreateUrlTree 实现,它支持诸如 ['/team', 33, {expand: true}, 'user', 11] 的矩阵参数写法)。

命令式导航:router.navigate()

RouterLink 负责模板中的声明式导航;但当导航由逻辑、用户操作或应用状态触发时,需要注入 Router 服务在 TypeScript 代码中完成命令式导航。通过注入 Router,你可以动态切换路由、传递参数并精细控制导航行为。

router.navigate() 接收一个 URL 路径段数组作为命令:

import {Router} from '@angular/router';

@Component({
  selector: 'app-dashboard',
  template: ` <button (click)="navigateToProfile()">View Profile</button> `,
})
export class AppDashboard {
  private router = inject(Router);

  navigateToProfile() {
    // 标准导航
    this.router.navigate(['/profile']);

    // 携带路由参数
    this.router.navigate(['/users', userId]);

    // 携带查询参数
    this.router.navigate(['/search'], {
      queryParams: {category: 'books', sort: 'price'},
    });

    // 携带矩阵参数
    this.router.navigate(['/products', {featured: true, onSale: true}]);
  }
}

router.navigate() 支持简单与复杂两类导航场景:可传路由参数、查询参数,并通过可选的 NavigationExtras 控制导航行为。查询参数的读取方式可继续阅读 read-route-state.md

从源码看(router.ts),navigate() 的职责很纯粹:先调用 createUrlTree(commands, extras) 把命令数组解析成 UrlTree,再把结果交给 navigateByUrl()

navigate(commands: readonly any[], extras: NavigationExtras = {skipLocationChange: false}): Promise<boolean> {
  validateCommands(commands);
  return this.navigateByUrl(this.createUrlTree(commands, extras), extras);
}

因此 navigate()RouterLink 在底层共享同一套命令解析逻辑——createUrlTree() 是理解一切导航路径语义的枢纽。

基于 currentRoute 做相对导航:relativeTo

你可以基于组件在路由树中的位置,用 relativeTo 选项构建动态相对导航路径relativeTo 接收注入的 ActivatedRoute,此时命令数组将相对该路由解析:

import {Router, ActivatedRoute} from '@angular/router';

@Component({
  selector: 'app-user-detail',
  template: `
    <button (click)="navigateToEdit()">Edit User</button>
    <button (click)="navigateToParent()">Back to List</button>
  `,
})
export class UserDetail {
  private route = inject(ActivatedRoute);
  private router = inject(Router);

  // 导航到同级(兄弟)路由
  navigateToEdit() {
    // 当前: /users/123
    // 目标: /users/123/edit
    this.router.navigate(['edit'], {relativeTo: this.route});
  }

  // 导航到父级
  navigateToParent() {
    // 当前: /users/123
    // 目标: /users
    this.router.navigate(['..'], {relativeTo: this.route});
  }

  navigateToList() {
    // Angular 将命令数组解析为相对当前路由的单条导航路径。
    // 当前: /users/123
    // 结果: /users/list
    this.router.navigate(['..', 'list'], {relativeTo: this.route});
  }
}

多级上溯的边界规则

向上导航多个层级时,所有 ..必须集中在命令数组的第一个元素中。Router 只会从第一个命令字符串解析 ..,后续数组元素一律被当作字面路径段处理:

// 当前: /team/123/users/456
// 结果: /team/123/settings
this.router.navigate(['../../settings'], {relativeTo: this.route});

使用 relativeTo 时禁止前导斜杠

当使用 relativeTo 时,第一个命令绝不能以 / 开头。前导 / 会把导航切换为绝对导航,relativeTo 将被完全忽略:

// 当前: /team/123/users/456
// 结果: /team/123/users/456/edit
this.router.navigate(['edit'], {relativeTo: this.route});
// 当前: /team/123/users/456
// 前导 '/' 触发绝对导航 —— relativeTo 被忽略
// 结果: /edit
this.router.navigate(['/edit'], {relativeTo: this.route});

命令式导航:router.navigateByUrl()

router.navigateByUrl() 直接接收 URL 路径字符串而非命令数组,适合场景是:你已掌握一条完整 URL 路径、需要执行绝对式导航,尤其是处理外部来源 URL或**深链接(deep linking)**时。

// 标准路由导航
router.navigateByUrl('/products');

// 嵌套路由
router.navigateByUrl('/products/featured');

// 带参数与片段的完整 URL
router.navigateByUrl('/products/123?view=details#reviews');

// 查询参数导航
router.navigateByUrl('/search?category=books&sortBy=price');

// 矩阵参数
router.navigateByUrl('/sales-awesome;isOffer=true;showModal=false');

navigate() 的签名对应,navigateByUrl() 同样接收 NavigationBehaviorOptions(定义于 models.ts),源码位于 router.ts

用 replaceUrl 替换历史记录

某些场景下你需要替换当前 URL 的历史记录条目(而不是新增一条),例如防止用户通过"返回"回到一个中间步骤页。此时传入带 replaceUrl 的配置对象即可:

// 在 history 中替换当前 URL
router.navigateByUrl('/checkout', {
  replaceUrl: true,
});

显示与路由不匹配的地址栏 URL:browserUrl

navigateByUrl 还支持传入 browserUrl 选项,让浏览器地址栏显示的 URL实际用于路由匹配的 URL不同。

典型场景是"重定向用户到另一条路由(如错误页),但地址栏保持用户最初访问的 URL 不变":

router.navigateByUrl('/not-found', {browserUrl: '/products/missing-item'});

此时 Angular 实际导航并渲染 /not-found 路由,而地址栏展示的仍是 /products/missing-item

NOTE:browserUrl 仅影响浏览器地址栏中呈现的内容。

models.ts 的接口注释可读到更完整的语义约束:browserUrl 只会改变地址栏展示,不会改变 Router 内部状态paramsdata 等仍来自用于匹配路由的内部 URL;这一点与 skipLocationChange 等让"浏览器 URL 与 Router 状态不一致"的机制相同。另外该选项是直接使用、不经过 UrlHandlingStrategy 处理,因此也能用来覆盖 UrlHandlingStrategy.merge 可能产生的地址栏差异。官方注释还给出了配合守卫(guard)使用的经典模式——未登录时用 RedirectCommand 重定向到 404 页,同时把地址栏 URL 保留为当前导航目标。

RouterLink 的 browserUrl 输入

RouterLink 指令同样提供 browserUrl 输入,让你控制点击链接后地址栏展示的 URL,且与实际路由导航互不影响。

<!-- 导航到 /dashboard,但地址栏显示 /home -->
<a [routerLink]="['/dashboard']" [browserUrl]="'/home'">Go to Dashboard</a>

也可以绑定一个 UrlTree,满足更动态的场景:

import {Component, inject} from '@angular/core';
import {Router, RouterLink, UrlTree} from '@angular/router';

@Component({
  template: `
    <a [routerLink]="['/products', product.id]" [browserUrl]="displayUrl">
      {{ product.name }}
    </a>
  `,
  imports: [RouterLink],
})
export class ProductList {
  private router = inject(Router);

  product = {id: 42, name: 'Widget'};

  // 生成一个 UrlTree 用于地址栏展示
  displayUrl: UrlTree = this.router.createUrlTree(['/products', 'widget']);
}

在指令实现中(router_link.ts),browserUrl 通过 input<UrlTree | string | undefined>() 声明,最终会被并入传给 navigateByUrl 的 extras 对象,与 skipLocationChangereplaceUrlstateinfo 一起作为导航行为选项下发。

源码对照与验证

本指南涉及的功能均可在仓库中找到实现与测试证据:

主题 实现位置 说明
RouterLink 指令 router_link.ts 声明式导航指令,包含 routerLinkbrowserUrlqueryParamsskipLocationChange 等输入
Router.navigate() router.ts 命令数组 → createUrlTreenavigateByUrl 的完整链路
Router.createUrlTree() router.ts 集中解析字符串/数组命令、relativeTo、矩阵参数等语义
导航行为选项 models.ts NavigationBehaviorOptionsbrowserUrlreplaceUrlstate 等的详细契约
ActivatedRoute/ActivatedRouteSnapshot router_state.ts 相对导航 relativeTo 的数据来源
集成测试 router_links.spec.ts 覆盖含 browserUrl 的锚点点击导航,断言地址栏路径为 /custom

其中测试用例(should support browserUrl)明确验证了:使用带 browserUrlRouterLink 点击后,应用内容正确切换目标路由,同时 location.path() 返回的是 browserUrl 指定的地址——这从集成层面佐证了"导航 URL 与展示 URL 分离"的行为。

小结与后续

选择导航 API 的实用准则:

  • 模板内固定链接(导航菜单、页脚等)→ 用 RouterLink 声明式写法;
  • 纯字符串完整 URL / 外部传入 / 深链接 → 用 router.navigateByUrl()
  • 需要动态拼接路径段、参数,或做相对路由导航 → 用 router.navigate() + relativeTo
  • 需要控制地址栏与真实路由不一致 → 两类 API 均支持 browserUrl(指令输入或导航选项)。

接下来可继续学习如何 读取路由状态,基于 URL、参数与快照构建响应式、感知上下文的组件;也可以进一步阅读路由指南的其他篇章,如 定义路由路由引用常见路由任务

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