本章目标:深入理解Angular路由机制,掌握路由配置、导航守卫、路由参数传递、懒加载和嵌套路由等核心功能。
Angular路由核心概念
路由模块
配置路由规则
路由导航
页面间跳转
导航守卫
路由访问控制
什么是Angular路由?
Angular路由是一个强大的导航系统,它允许我们在单页面应用(SPA)中实现页面间的无缝切换,同时保持应用的响应性和性能。
基本功能
- 定义URL路径与组件的映射关系
- 实现单页面应用的无刷新导航
- 管理浏览器的历史记录
- 支持前进/后退按钮
高级特性
- 嵌套路由和子路由
- 路由参数和查询参数
- 懒加载模块
- 路由动画
安全控制
- 导航守卫(路由保护)
- 路由数据预加载
- 路由事件监听
- 错误处理
1. 创建路由模块
使用Angular CLI可以快速创建带有路由功能的应用,也可以手动配置路由模块。
# 创建新项目时启用路由
ng new my-app --routing
# 为现有模块添加路由
ng generate module app-routing --flat --module=app
# 创建带路由的功能模块
ng generate module admin --routing
基本路由配置
路由模块负责定义URL路径与组件之间的映射关系:
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
// 导入组件
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { ContactComponent } from './contact/contact.component';
import { ProductsComponent } from './products/products.component';
import { ProductDetailComponent } from './products/product-detail/product-detail.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
// 定义路由配置
const routes: Routes = [
// 默认路由重定向
{ path: '', redirectTo: '/home', pathMatch: 'full' },
// 基本路由
{ path: 'home', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: 'contact', component: ContactComponent },
// 带参数的路由
{ path: 'products', component: ProductsComponent },
{ path: 'products/:id', component: ProductDetailComponent },
// 通配符路由(必须放在最后)
{ path: '**', component: PageNotFoundComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
路由出口
在根组件模板中添加<router-outlet></router-outlet>标签,用于显示路由内容:
<!-- app.component.html -->
<div class="app-container">
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" [routerLink]="['/']">MyApp</a>
<div class="navbar-nav">
<a class="nav-link" [routerLink]="['/home']">首页</a>
<a class="nav-link" [routerLink]="['/about']">关于</a>
<a class="nav-link" [routerLink]="['/products']">产品</a>
<a class="nav-link" [routerLink]="['/contact']">联系</a>
</div>
</div>
</nav>
<!-- 路由出口 -->
<div class="container mt-4">
<router-outlet></router-outlet>
</div>
<!-- 页脚 -->
<footer class="mt-5 text-center">
<p>© 2024 MyApp</p>
</footer>
</div>
2. 路由导航
Angular提供两种方式进行路由导航:模板导航和编程导航。
路由导航流程图
用户操作
点击链接或按钮
路由匹配
Angular匹配路由配置
守卫检查
执行导航守卫逻辑
组件加载
加载对应组件到出口
模板导航
使用routerLink指令在模板中创建导航链接。
<!-- 基本导航 -->
<a routerLink="/home">首页</a>
<a [routerLink]="['/about']">关于</a>
<!-- 带CSS类绑定 -->
<a [routerLink]="['/contact']"
routerLinkActive="active"
[routerLinkActiveOptions]="{exact: true}">
联系我们
</a>
<!-- 带参数的导航 -->
<a [routerLink]="['/products', productId]">
产品详情
</a>
<!-- 带查询参数的导航 -->
<a [routerLink]="['/search']"
[queryParams]="{q: 'angular', page: 1}">
搜索
</a>
<!-- 按钮导航 -->
<button [routerLink]="['/dashboard']">
仪表板
</button>
<!-- 导航链接样式 -->
<nav>
<a *ngFor="let link of navLinks"
[routerLink]="link.path"
routerLinkActive="active-link">
{{ link.label }}
</a>
</nav>
编程导航
在组件类中使用Router服务进行导航。
import { Component } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-navigation-demo',
template: `
<button (click)="goToHome()">去首页</button>
<button (click)="goToProduct(123)">查看产品123</button>
<button (click)="goBack()">返回</button>
`
})
export class NavigationDemoComponent {
constructor(private router: Router) {}
// 基本导航
goToHome(): void {
this.router.navigate(['/home']);
}
// 带参数的导航
goToProduct(productId: number): void {
this.router.navigate(['/products', productId]);
}
// 带查询参数的导航
goToSearch(keyword: string): void {
this.router.navigate(['/search'], {
queryParams: { q: keyword },
queryParamsHandling: 'merge' // 合并现有查询参数
});
}
// 相对路径导航
goToRelative(): void {
this.router.navigate(['../sibling'], { relativeTo: this.route });
}
// 导航并保留查询参数
goToAbout(): void {
this.router.navigate(['/about'], {
queryParamsHandling: 'preserve'
});
}
// 导航到根路径
goToRoot(): void {
this.router.navigate(['/']);
}
// 浏览器后退
goBack(): void {
this.router.navigate(['../']); // 上一级
// 或使用 location.back()
}
// 浏览器前进
goForward(): void {
// 使用 location.forward()
}
// 获取当前URL
getCurrentUrl(): string {
return this.router.url;
}
}
路由导航实时演示
3. 路由参数
路由参数允许我们在URL中传递数据,Angular支持路径参数、查询参数和片段参数。
| 参数类型 | 语法 | 用途 | 示例 |
|---|---|---|---|
| 路径参数 | /path/:param |
标识资源的唯一标识 | /products/123 |
| 查询参数 | ?key=value |
过滤、排序或配置选项 | /search?q=angular&page=1 |
| 片段参数 | #fragment |
页面内跳转(锚点) | /about#team |
获取路由参数
在组件中通过ActivatedRoute服务获取路由参数:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
@Component({
selector: 'app-product-detail',
template: `
<h2>产品详情</h2>
<p>产品ID: {{ productId }}</p>
<p>搜索关键词: {{ searchQuery }}</p>
`
})
export class ProductDetailComponent implements OnInit {
productId!: string;
searchQuery!: string;
constructor(private route: ActivatedRoute) {}
ngOnInit(): void {
// 获取路径参数(方法1:快照)
this.productId = this.route.snapshot.params['id'];
// 获取查询参数(方法1:快照)
this.searchQuery = this.route.snapshot.queryParams['q'];
// 获取路径参数(方法2:观察者 - 推荐)
this.route.params.subscribe((params: Params) => {
this.productId = params['id'];
// 当参数变化时,重新加载数据
this.loadProductData(this.productId);
});
// 获取查询参数(方法2:观察者)
this.route.queryParams.subscribe((params: Params) => {
this.searchQuery = params['q'];
// 当查询参数变化时,执行搜索
if (this.searchQuery) {
this.performSearch(this.searchQuery);
}
});
// 获取片段参数
this.route.fragment.subscribe(fragment => {
console.log('页面片段:', fragment);
if (fragment) {
this.scrollToElement(fragment);
}
});
// 获取所有参数
const paramMap = this.route.snapshot.paramMap;
const queryParamMap = this.route.snapshot.queryParamMap;
this.productId = paramMap.get('id') || '';
this.searchQuery = queryParamMap.get('q') || '';
}
private loadProductData(id: string): void {
// 加载产品数据
console.log('加载产品数据:', id);
}
private performSearch(query: string): void {
// 执行搜索
console.log('执行搜索:', query);
}
private scrollToElement(elementId: string): void {
// 滚动到指定元素
const element = document.getElementById(elementId);
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
}
}
}
路由参数传递示例
路径参数配置
// 路由配置
const routes: Routes = [
{ path: 'products/:id', component: ProductDetailComponent },
{ path: 'users/:userId/posts/:postId', component: PostComponent }
];
// 导航到带参数的路径
this.router.navigate(['/products', product.id]);
// 获取参数
this.route.params.subscribe(params => {
const productId = params['id'];
});
查询参数配置
// 导航带查询参数
this.router.navigate(['/search'], {
queryParams: {
q: 'angular',
page: 1,
sort: 'name'
}
});
// 合并查询参数
this.router.navigate(['/search'], {
queryParams: { page: 2 },
queryParamsHandling: 'merge'
});
// 保留查询参数
this.router.navigate(['/results'], {
queryParamsHandling: 'preserve'
});
// 获取查询参数
this.route.queryParams.subscribe(params => {
const query = params['q'];
const page = params['page'];
});
4. 导航守卫
导航守卫允许我们在路由导航发生之前、期间和之后执行自定义逻辑,用于权限控制、数据预加载等。
导航守卫类型
CanActivate
控制是否可以访问路由(进入路由前)
用途:登录验证、权限检查
CanActivateChild
控制是否可以访问子路由
用途:子路由权限控制
CanDeactivate
控制是否可以离开当前路由
用途:表单未保存提示
Resolve
在路由激活前预加载数据
用途:数据预加载
CanLoad
控制是否可以加载模块(懒加载)
用途:模块加载权限控制
实现导航守卫
// auth.guard.ts - 认证守卫
import { Injectable } from '@angular/core';
import {
CanActivate,
ActivatedRouteSnapshot,
RouterStateSnapshot,
Router,
UrlTree
} from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthService,
private router: Router
) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
// 检查用户是否已登录
if (this.authService.isLoggedIn) {
return true;
}
// 如果未登录,重定向到登录页
// 同时保存当前URL,以便登录后返回
this.authService.redirectUrl = state.url;
return this.router.parseUrl('/login');
}
}
// admin.guard.ts - 管理员权限守卫
@Injectable({ providedIn: 'root' })
export class AdminGuard implements CanActivate {
constructor(private authService: AuthService) {}
canActivate(): boolean {
// 检查用户角色是否为管理员
const user = this.authService.currentUser;
return user?.role === 'admin';
}
}
// unsaved-changes.guard.ts - 未保存更改守卫
@Injectable({ providedIn: 'root' })
export class UnsavedChangesGuard implements CanDeactivate<CanComponentDeactivate> {
canDeactivate(
component: CanComponentDeactivate,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState?: RouterStateSnapshot
): Observable<boolean> | Promise<boolean> | boolean {
// 调用组件的canDeactivate方法
return component.canDeactivate ?
component.canDeactivate() : true;
}
}
// 在组件中实现CanComponentDeactivate接口
export interface CanComponentDeactivate {
canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}
@Component({...})
export class EditProductComponent implements CanComponentDeactivate {
hasUnsavedChanges = false;
canDeactivate(): boolean | Observable<boolean> {
if (!this.hasUnsavedChanges) {
return true;
}
// 如果有未保存的更改,显示确认对话框
return confirm('您有未保存的更改,确定要离开吗?');
}
}
在路由配置中使用守卫
const routes: Routes = [
// 公开路由(不需要守卫)
{ path: 'home', component: HomeComponent },
{ path: 'login', component: LoginComponent },
// 需要认证的路由
{
path: 'profile',
component: ProfileComponent,
canActivate: [AuthGuard] // 使用认证守卫
},
// 需要管理员权限的路由
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard, AdminGuard] // 多个守卫
},
// 带数据预加载的路由
{
path: 'product/:id',
component: ProductDetailComponent,
resolve: {
product: ProductResolver // 使用数据解析器
}
},
// 子路由守卫
{
path: 'dashboard',
component: DashboardComponent,
canActivateChild: [AuthGuard], // 子路由守卫
children: [
{ path: 'stats', component: StatsComponent },
{ path: 'settings', component: SettingsComponent }
]
},
// 懒加载模块守卫
{
path: 'reports',
canLoad: [AuthGuard], // 模块加载守卫
loadChildren: () => import('./reports/reports.module')
.then(m => m.ReportsModule)
}
];
5. 懒加载模块
懒加载可以显著提高应用的初始加载速度,通过按需加载功能模块来减少初始包大小。
懒加载优势
减少初始加载时间
只加载当前路由所需的代码
代码分割
将应用拆分为多个独立的包
优化内存使用
按需加载和卸载模块
更好的用户体验
快速显示初始界面
配置懒加载模块
// app-routing.module.ts
const routes: Routes = [
// 主路由
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
// 懒加载功能模块
{
path: 'products',
loadChildren: () => import('./products/products.module')
.then(m => m.ProductsModule)
},
{
path: 'admin',
loadChildren: () => import('./admin/admin.module')
.then(m => m.AdminModule),
canLoad: [AuthGuard, AdminGuard] // 模块加载守卫
},
{
path: 'user',
loadChildren: () => import('./user/user.module')
.then(m => m.UserModule)
},
// 通配符路由(必须在最后)
{ path: '**', component: PageNotFoundComponent }
];
// products.module.ts
@NgModule({
declarations: [
ProductsComponent,
ProductDetailComponent,
ProductListComponent
],
imports: [
CommonModule,
ProductsRoutingModule // 产品模块有自己的路由
]
})
export class ProductsModule { }
// products-routing.module.ts
const routes: Routes = [
{ path: '', component: ProductsComponent },
{ path: ':id', component: ProductDetailComponent }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ProductsRoutingModule { }
预加载策略
Angular提供了多种预加载策略来平衡性能和用户体验:
// 使用预加载策略
@NgModule({
imports: [RouterModule.forRoot(routes, {
// 1. 不预加载(默认)
preloadingStrategy: NoPreloading,
// 2. 预加载所有模块
preloadingStrategy: PreloadAllModules,
// 3. 自定义预加载策略
preloadingStrategy: CustomPreloadingStrategy,
// 4. 其他配置选项
enableTracing: true, // 启用路由调试
useHash: false, // 是否使用hash模式
scrollPositionRestoration: 'enabled' // 滚动位置恢复
})],
exports: [RouterModule]
})
export class AppRoutingModule { }
// 自定义预加载策略
@Injectable({ providedIn: 'root' })
export class CustomPreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
// 检查路由是否有data.preload标记
if (route.data && route.data['preload'] === true) {
return load();
}
// 否则不预加载
return of(null);
}
}
// 在路由配置中标记需要预加载的模块
const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module')
.then(m => m.DashboardModule),
data: { preload: true } // 标记为需要预加载
},
{
path: 'settings',
loadChildren: () => import('./settings/settings.module')
.then(m => m.SettingsModule)
// 不标记,使用懒加载
}
];
6. 嵌套路由和辅助路由
嵌套路由允许在组件内部定义子路由,辅助路由允许在同一页面显示多个路由出口。
嵌套路由配置
// 路由配置
const routes: Routes = [
{
path: 'dashboard',
component: DashboardComponent,
children: [ // 定义子路由
{
path: '',
redirectTo: 'overview',
pathMatch: 'full'
},
{
path: 'overview',
component: OverviewComponent
},
{
path: 'analytics',
component: AnalyticsComponent
},
{
path: 'reports',
component: ReportsComponent
},
{
path: 'settings',
component: SettingsComponent
}
]
}
];
// dashboard.component.html
<div class="dashboard">
<!-- 侧边栏导航 -->
<nav class="sidebar">
<a [routerLink]="['overview']" routerLinkActive="active">
概览
</a>
<a [routerLink]="['analytics']" routerLinkActive="active">
分析
</a>
<a [routerLink]="['reports']" routerLinkActive="active">
报告
</a>
<a [routerLink]="['settings']" routerLinkActive="active">
设置
</a>
</nav>
<!-- 子路由出口 -->
<div class="content">
<router-outlet></router-outlet>
</div>
</div>
辅助路由(命名路由出口)
// 路由配置
const routes: Routes = [
// 主路由
{ path: 'home', component: HomeComponent },
// 辅助路由
{
path: 'sidebar',
component: SidebarComponent,
outlet: 'sidebar' // 命名路由出口
},
{
path: 'chat',
component: ChatComponent,
outlet: 'chat'
}
];
// app.component.html
<div class="app-container">
<!-- 主路由出口 -->
<div class="main-content">
<router-outlet></router-outlet>
</div>
<!-- 辅助路由出口 -->
<div class="sidebar">
<router-outlet name="sidebar"></router-outlet>
</div>
<div class="chat-widget">
<router-outlet name="chat"></router-outlet>
</div>
</div>
// 导航到辅助路由
// 同时激活主路由和辅助路由
this.router.navigate([
{ outlets: { primary: ['home'], sidebar: ['sidebar'] } }
]);
// 只导航辅助路由
this.router.navigate([{ outlets: { sidebar: ['chat'] } }]);
// 关闭辅助路由
this.router.navigate([{ outlets: { sidebar: null } }]);
路由最佳实践
推荐做法
- 使用懒加载模块来提高初始加载性能
- 为路由配置合理的预加载策略
- 使用导航守卫保护敏感路由
- 合理组织路由配置,保持代码清晰
- 使用路由参数而不是组件属性传递数据
- 为路由配置添加合适的数据(title、breadcrumb等)
- 使用
routerLinkActive高亮当前激活路由 - 处理路由错误和404页面
应避免的做法
- 避免在组件构造函数中依赖路由参数(使用ngOnInit)
- 不要忘记取消订阅路由参数观察者
- 避免创建过于复杂的嵌套路由结构
- 不要在守卫中执行耗时操作(使用Resolve预加载)
- 避免硬编码路由路径(使用路由常量)
- 不要忘记处理路由守卫的返回类型(Observable/Promise)
- 避免在路由之间传递大量数据(使用服务)
- 不要忽略路由安全性(始终验证权限)
路由配置速查表
| 配置选项 | 描述 | 示例 |
|---|---|---|
| 基本路由 | 路径到组件的映射 | { path: 'home', component: HomeComponent } |
| 重定向路由 | 重定向到其他路径 | { path: '', redirectTo: '/home', pathMatch: 'full' } |
| 路径参数 | URL中的参数 | { path: 'products/:id', component: ProductDetailComponent } |
| 子路由 | 嵌套路由配置 | children: [{ path: 'details', component: DetailsComponent }] |
| 懒加载 | 按需加载模块 | loadChildren: () => import('./module').then(m => m.Module) |
| 路由守卫 | 路由访问控制 | canActivate: [AuthGuard] |
| 路由数据 | 附加到路由的数据 | data: { title: '首页', requiresAuth: true } |
| 通配符路由 | 匹配所有路径 | { path: '**', component: NotFoundComponent } |
本章总结
通过本章学习,你应该掌握了:
- 路由模块创建:配置路由规则和映射关系
- 路由导航:使用
routerLink和Router服务进行导航 - 路由参数:传递和获取路径参数、查询参数
- 导航守卫:实现路由保护和权限控制
- 懒加载:按需加载模块以提高性能
- 嵌套路由:配置子路由和命名路由出口
- 最佳实践:编写高效、安全的路由配置