Angular 路由导航完全指南:RouterLink 声明式导航与 Router 命令式导航实战
本文基于 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 是一个实现了 OnChanges、OnDestroy 的指令类,定义于 router_link.ts,除 routerLink 外还暴露了 queryParams、fragment、state、relativeTo、skipLocationChange、replaceUrl、target 等一组输入,本指南后续会介绍其中部分。
绝对 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.ts 的 createUrlTree 实现,它支持诸如 ['/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 内部状态,params、data 等仍来自用于匹配路由的内部 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 对象,与 skipLocationChange、replaceUrl、state、info 一起作为导航行为选项下发。
源码对照与验证
本指南涉及的功能均可在仓库中找到实现与测试证据:
| 主题 | 实现位置 | 说明 |
|---|---|---|
RouterLink 指令 |
router_link.ts | 声明式导航指令,包含 routerLink、browserUrl、queryParams、skipLocationChange 等输入 |
Router.navigate() |
router.ts | 命令数组 → createUrlTree → navigateByUrl 的完整链路 |
Router.createUrlTree() |
router.ts | 集中解析字符串/数组命令、relativeTo、矩阵参数等语义 |
| 导航行为选项 | models.ts | NavigationBehaviorOptions 中 browserUrl、replaceUrl、state 等的详细契约 |
ActivatedRoute/ActivatedRouteSnapshot |
router_state.ts | 相对导航 relativeTo 的数据来源 |
| 集成测试 | router_links.spec.ts | 覆盖含 browserUrl 的锚点点击导航,断言地址栏路径为 /custom |
其中测试用例(should support browserUrl)明确验证了:使用带 browserUrl 的 RouterLink 点击后,应用内容正确切换目标路由,同时 location.path() 返回的是 browserUrl 指定的地址——这从集成层面佐证了"导航 URL 与展示 URL 分离"的行为。
小结与后续
选择导航 API 的实用准则:
- 模板内固定链接(导航菜单、页脚等)→ 用
RouterLink声明式写法; - 纯字符串完整 URL / 外部传入 / 深链接 → 用
router.navigateByUrl(); - 需要动态拼接路径段、参数,或做相对路由导航 → 用
router.navigate()+relativeTo; - 需要控制地址栏与真实路由不一致 → 两类 API 均支持
browserUrl(指令输入或导航选项)。
接下来可继续学习如何 读取路由状态,基于 URL、参数与快照构建响应式、感知上下文的组件;也可以进一步阅读路由指南的其他篇章,如 定义路由、路由引用 与 常见路由任务。
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 StartedRust0623
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