11_路由
路由:构建单页应用导航系统
路由是单页应用(SPA)的核心机制,负责管理不同视图之间的导航,而无需重新加载整个页面。Angular提供了强大的路由模块,支持动态路由、嵌套路由、路由守卫、延迟加载等高级特性。Angular v20进一步优化了路由系统,强化了与独立组件的集成,并简化了配置方式。本章将详细讲解路由的核心概念与实战技巧。
1. 路由基础:配置与导航
1.1 路由配置核心要素
Angular路由通过配置路由规则(Routes数组)定义URL与组件的映射关系。v20推荐使用provideRouter函数替代传统的RouterModule,实现更简洁的路由配置。
基本路由配置
// app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { ContactComponent } from './contact/contact.component';
// 路由规则数组
export const routes: Routes = [
{ path: '', component: HomeComponent }, // 首页(默认路由)
{ path: 'about', component: AboutComponent }, // 关于页
{ path: 'contact', component: ContactComponent }, // 联系页
{ path: '**', redirectTo: '' } // 通配符路由(404页面)
];
在main.ts中注册路由配置:
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { provideRouter } from '@angular/router'; // 导入路由配置函数
import { routes } from './app.routes';
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes) // 配置路由服务
]
}).catch(err => console.error(err));
1.2 路由出口与导航链接
路由出口(Router Outlet)
router-outlet是路由视图的占位符,用于显示当前激活路由对应的组件:
// app.component.ts
import { Component } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, RouterLink], // 导入路由相关指令
template: `
<!-- 导航链接 -->
<nav>
<a routerLink="/">首页</a> |
<a routerLink="/about">关于我们</a> |
<a routerLink="/contact">联系我们</a>
</nav>
<!-- 路由出口:显示匹配的组件 -->
<router-outlet></router-outlet>
`
})
export class AppComponent {}
导航链接(Router Link)
routerLink指令用于创建导航链接,支持相对路径和绝对路径:
- 绝对路径:以
/开头,从根路由开始匹配(如routerLink="/about") - 相对路径:不以
/开头,相对于当前路由(如在/user页面中,routerLink="profile"等价于/user/profile)
还可以通过routerLinkActive指令高亮当前激活的导航项:
<a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">首页</a>
<a routerLink="/about" routerLinkActive="active">关于我们</a>
routerLinkActive="active":当链接对应的路由激活时,添加active类{ exact: true }:精确匹配(仅当URL完全匹配时才激活,适用于根路由)
2. 动态路由与参数传递
实际应用中经常需要根据动态参数(如ID)加载不同内容(如用户详情、商品详情),这需要使用动态路由。
2.1 定义动态路由
在路由配置中使用:参数名定义动态片段:
// app.routes.ts
import { UserDetailComponent } from './user-detail/user-detail.component';
export const routes: Routes = [
// ...其他路由
{ path: 'users/:id', component: UserDetailComponent } // 动态路由:id为参数
];
2.2 获取路由参数
通过ActivatedRoute服务获取动态路由参数,v20推荐结合toSignal将参数转换为Signal:
// user-detail.component.ts
import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { toSignal } from '@angular/core/rxjs-interop';
import { UserService } from './user.service';
@Component({
selector: 'app-user-detail',
standalone: true,
template: `
@if (user()) {
<h2>用户详情 #{{ user()?.id }}</h2>
<p>姓名:{{ user()?.name }}</p>
<p>邮箱:{{ user()?.email }}</p>
} @else if (error()) {
<p class="error">{{ error() }}</p>
} @else {
<p>加载中...</p>
}
`
})
export class UserDetailComponent {
private route = inject(ActivatedRoute);
private userService = inject(UserService);
// 获取路由参数(id)并转换为Signal
userId = toSignal(this.route.paramMap.pipe(
map(params => params.get('id'))
));
// 根据id加载用户数据
user = toSignal(
this.userId.pipe(
switchMap(id => id ? this.userService.getUserById(+id) : of(null)),
catchError(err => {
this.error.set('加载用户失败');
return of(null);
})
),
{ initialValue: null }
);
error = signal('');
}
2.3 路由查询参数
除了动态片段,还可以通过查询参数(如?page=1&limit=10)传递额外信息:
// 导航时添加查询参数
<a [routerLink]="['/users']" [queryParams]="{ page: 1, limit: 10 }">用户列表</a>
// 在组件中获取查询参数
import { ActivatedRoute } from '@angular/router';
import { toSignal } from '@angular/core/rxjs-interop';
@Component({ /* ... */ })
export class UserListComponent {
private route = inject(ActivatedRoute);
// 获取查询参数
queryParams = toSignal(this.route.queryParamMap, { initialValue: new ParamMap({}) });
ngOnInit() {
const page = this.queryParams().get('page'); // 获取page参数
const limit = this.queryParams().get('limit'); // 获取limit参数
}
}
3. 嵌套路由:构建复杂页面结构
嵌套路由允许在一个组件中嵌套另一个路由视图,适合构建具有层级关系的页面(如仪表板、带侧边栏的页面)。
3.1 配置嵌套路由
使用children属性定义子路由:
// app.routes.ts
import { DashboardComponent } from './dashboard/dashboard.component';
import { DashboardHomeComponent } from './dashboard/home/home.component';
import { DashboardStatsComponent } from './dashboard/stats/stats.component';
import { DashboardSettingsComponent } from './dashboard/settings/settings.component';
export const routes: Routes = [
// ...其他路由
{
path: 'dashboard',
component: DashboardComponent,
// 子路由配置
children: [
{ path: '', redirectTo: 'home', pathMatch: 'full' }, // 默认子路由
{ path: 'home', component: DashboardHomeComponent },
{ path: 'stats', component: DashboardStatsComponent },
{ path: 'settings', component: DashboardSettingsComponent }
]
}
];
3.2 实现嵌套路由视图
在父组件(DashboardComponent)中添加router-outlet显示子路由组件:
// dashboard.component.ts
import { Component } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [RouterOutlet, RouterLink],
template: `
<div class="dashboard">
<aside>
<h2>仪表盘</h2>
<nav>
<a routerLink="home">首页</a>
<a routerLink="stats">统计数据</a>
<a routerLink="settings">设置</a>
</nav>
</aside>
<main>
<!-- 子路由出口:显示子组件 -->
<router-outlet></router-outlet>
</main>
</div>
`,
styles: [`
.dashboard { display: flex; gap: 20px; }
aside { width: 200px; background: #f5f5f5; padding: 1rem; }
main { flex: 1; padding: 1rem; }
`]
})
export class DashboardComponent {}
访问/dashboard会自动重定向到/dashboard/home,并在父组件的router-outlet中显示DashboardHomeComponent。
4. 路由守卫:控制路由访问
路由守卫用于在导航发生前、发生中或发生后执行特定逻辑,如验证用户权限、确认表单未保存、加载必要数据等。
4.1 常用路由守卫类型
| 守卫接口 | 作用时机 | 典型用途 |
|---|---|---|
CanActivate |
进入路由前 | 验证用户是否有权限访问 |
CanDeactivate |
离开路由前 | 确认是否放弃未保存的更改 |
Resolve |
进入路由前 | 预加载路由所需数据 |
CanLoad |
加载懒加载模块前 | 控制是否允许加载模块 |
4.2 实现权限守卫(CanActivate)
创建认证守卫,只允许登录用户访问特定路由:
// auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthService,
private router: Router
) {}
// 决定是否允许激活路由
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean {
if (this.authService.isLoggedIn()) {
return true; // 已登录,允许访问
} else {
// 未登录,重定向到登录页,并记录目标URL
this.router.navigate(['/login'], {
queryParams: { returnUrl: state.url }
});
return false; // 阻止访问
}
}
}
在路由配置中应用守卫:
// app.routes.ts
import { AuthGuard } from './auth.guard';
export const routes: Routes = [
{
path: 'dashboard',
component: DashboardComponent,
children: [/* ... */],
canActivate: [AuthGuard] // 应用认证守卫
}
];
4.3 实现离开确认守卫(CanDeactivate)
防止用户意外离开未保存的表单页面:
// unsaved-changes.guard.ts
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { FormComponent } from './form/form.component';
// 定义表单组件需实现的接口
export interface CanComponentDeactivate {
canDeactivate: () => boolean | Promise<boolean>;
}
@Injectable({ providedIn: 'root' })
export class UnsavedChangesGuard implements CanDeactivate<CanComponentDeactivate> {
canDeactivate(component: CanComponentDeactivate): boolean | Promise<boolean> {
// 调用组件的canDeactivate方法
return component.canDeactivate ? component.canDeactivate() : true;
}
}
在表单组件中实现接口:
// form.component.ts
import { Component } from '@angular/core';
import { FormGroup, FormControl, ReactiveFormsModule } from '@angular/forms';
import { CanComponentDeactivate } from './unsaved-changes.guard';
@Component({
selector: 'app-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `<!-- 表单内容 -->`
})
export class FormComponent implements CanComponentDeactivate {
form = new FormGroup({
name: new FormControl('')
});
isSaved = false;
// 实现CanComponentDeactivate接口
canDeactivate(): boolean | Promise<boolean> {
if (this.form.dirty && !this.isSaved) {
// 表单已修改且未保存,询问用户
return confirm('您有未保存的更改,确定要离开吗?');
}
return true;
}
onSave() {
this.isSaved = true;
// 保存逻辑...
}
}
应用离开守卫:
// app.routes.ts
import { UnsavedChangesGuard } from './unsaved-changes.guard';
export const routes: Routes = [
{
path: 'form',
component: FormComponent,
canDeactivate: [UnsavedChangesGuard] // 应用离开守卫
}
];
5. 延迟加载:优化应用性能
延迟加载(Lazy Loading)允许在用户需要时才加载特定组件,减少初始加载时间,提升应用性能。Angular v20对独立组件的延迟加载支持更加完善。
5.1 延迟加载独立组件
使用loadComponent函数实现组件的延迟加载:
// app.routes.ts
import { Routes } from '@angular/router';
// 不直接导入组件,而是通过loadComponent动态加载
export const routes: Routes = [
// ...其他路由
{
path: 'admin',
// 延迟加载AdminComponent
loadComponent: () => import('./admin/admin.component')
.then(m => m.AdminComponent),
canActivate: [AuthGuard] // 结合守卫使用
}
];
5.2 带路由的延迟加载模块
对于复杂功能模块,可创建包含子路由的延迟加载模块:
// admin.routes.ts
import { Routes } from '@angular/router';
import { AdminDashboardComponent } from './admin-dashboard/admin-dashboard.component';
import { UserManagementComponent } from './user-management/user-management.component';
export const adminRoutes: Routes = [
{ path: '', component: AdminDashboardComponent },
{ path: 'users', component: UserManagementComponent }
];
// app.routes.ts
export const routes: Routes = [
// ...其他路由
{
path: 'admin',
// 延迟加载包含子路由的模块
loadChildren: () => import('./admin/admin.module')
.then(m => m.AdminModule)
}
];
AdminModule的实现:
// admin.module.ts
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { adminRoutes } from './admin.routes';
import { AdminDashboardComponent } from './admin-dashboard/admin-dashboard.component';
import { UserManagementComponent } from './user-management/user-management.component';
@NgModule({
imports: [
RouterModule.forChild(adminRoutes), // 配置子路由
AdminDashboardComponent,
UserManagementComponent
]
})
export class AdminModule {}
6. 编程式导航与路由状态
除了使用routerLink指令进行声明式导航,还可以通过Router服务实现编程式导航。
6.1 基本编程式导航
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-nav-buttons',
standalone: true,
template: `
<button (click)="goToHome()">首页</button>
<button (click)="goToUser(123)">用户123</button>
<button (click)="goBack()">返回</button>
`
})
export class NavButtonsComponent {
private router = inject(Router);
// 导航到首页
goToHome() {
this.router.navigate(['/']); // 等价于routerLink="/"
}
// 导航到带参数的路由
goToUser(id: number) {
this.router.navigate(['/users', id]); // 等价于routerLink="/users/123"
}
// 返回上一页
goBack() {
this.router.navigate(['../'], { relativeTo: this.route }); // 相对导航
// 或使用history.back()
// window.history.back();
}
}
6.2 传递状态数据
导航时可以通过state属性传递不显示在URL中的状态数据:
// 导航时传递状态
this.router.navigate(['/user', 123], {
state: { from: 'dashboard', timestamp: new Date().getTime() }
});
// 在目标组件中获取状态
import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({ /* ... */ })
export class UserDetailComponent {
constructor(private route: ActivatedRoute) {
// 获取导航状态
const navigationState = this.route.snapshot.navigationState;
console.log('来源:', navigationState?.from);
console.log('时间戳:', navigationState?.timestamp);
}
}
7. Angular v20路由优化特性
7.1 独立组件路由增强
v20简化了独立组件的路由配置,无需模块即可直接配置路由,减少样板代码:
// 独立组件直接作为路由目标,无需模块包装
export const routes: Routes = [
{ path: 'profile', component: ProfileComponent } // ProfileComponent是独立组件
];
7.2 树摇优化
v20的路由系统更加支持树摇(Tree-shaking),未使用的路由功能(如特定守卫、解析器)会被从最终构建产物中移除,减小应用体积。
7.3 与Signals深度集成
通过toSignal将路由参数、查询参数等转换为Signals,实现更高效的响应式更新:
// 路由参数转换为Signal
const userId = toSignal(
this.route.paramMap.pipe(map(params => params.get('id'))),
{ initialValue: null }
);
// 基于参数的响应式数据加载
const user = computed(() => {
if (userId()) {
return this.userService.getUser(+userId());
}
return null;
});
总结
路由是Angular单页应用的核心机制,通过本章学习,你已掌握:
- 路由的基本配置与导航方式,包括
provideRouter、router-outlet和routerLink - 动态路由与参数传递,以及如何获取路由参数和查询参数
- 嵌套路由的配置与实现,构建复杂页面结构
- 路由守卫的使用,控制路由访问和离开行为
- 延迟加载的实现,优化应用性能
- 编程式导航与路由状态管理
Angular v20的路由系统在独立组件支持、树摇优化和Signals集成方面的改进,使路由配置更加简洁高效。在实际开发中,合理设计路由结构、应用延迟加载和路由守卫,能显著提升应用的性能和用户体验。

浙公网安备 33010602011771号