Skip to main content

Angular路由与导航

Angular路由系统允许在用户导航应用程序的不同视图之间进行切换,它支持路由参数、嵌套路由、路由守卫和延迟加载等高级功能。

核心价值

Angular路由优势

  • 🧭 单页面应用:无需重新加载整个页面即可切换视图
  • 🔗 URL管理:提供清晰可共享的URL,支持书签和历史
  • 🧩 模块化路由:支持特性模块的路由分离
  • 🔄 延迟加载:按需加载模块,提升初始加载性能
  • 🛡️ 路由守卫:保护路由,实现权限控制

1. 路由基础

1.1 配置路由

在Angular应用中配置路由需要以下步骤:

  1. 创建路由模块:
app-routing.module.ts
typescript
1import { NgModule } from '@angular/core';
2import { RouterModule, Routes } from '@angular/router';
3
4import { HomeComponent } from './home/home.component';
5import { AboutComponent } from './about/about.component';
6import { ContactComponent } from './contact/contact.component';
7import { NotFoundComponent } from './not-found/not-found.component';
8
9const routes: Routes = [
10 { path: '', component: HomeComponent },
11 { path: 'home', redirectTo: '', pathMatch: 'full' },
12 { path: 'about', component: AboutComponent },
13 { path: 'contact', component: ContactComponent },
14 { path: '**', component: NotFoundComponent } // 通配符路由必须放在最后
15];
16
17@NgModule({
18 imports: [RouterModule.forRoot(routes)],
19 exports: [RouterModule]
20})
21export class AppRoutingModule { }
  1. 在主模块中导入路由模块:
app.module.ts
typescript
1import { NgModule } from '@angular/core';
2import { BrowserModule } from '@angular/platform-browser';
3import { AppRoutingModule } from './app-routing.module';
4import { AppComponent } from './app.component';
5// 导入组件
6import { HomeComponent } from './home/home.component';
7import { AboutComponent } from './about/about.component';
8import { ContactComponent } from './contact/contact.component';
9import { NotFoundComponent } from './not-found/not-found.component';
10
11@NgModule({
12 declarations: [
13 AppComponent,
14 HomeComponent,
15 AboutComponent,
16 ContactComponent,
17 NotFoundComponent
18 ],
19 imports: [
20 BrowserModule,
21 AppRoutingModule
22 ],
23 providers: [],
24 bootstrap: [AppComponent]
25})
26export class AppModule { }
  1. 添加路由出口和导航链接:
app.component.html
html
1<header>
2 <nav>
3 <ul>
4 <li><a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">首页</a></li>
5 <li><a routerLink="/about" routerLinkActive="active">关于我们</a></li>
6 <li><a routerLink="/contact" routerLinkActive="active">联系我们</a></li>
7 </ul>
8 </nav>
9</header>
10
11<main>
12 <!-- 路由出口 - 匹配的组件将显示在这里 -->
13 <router-outlet></router-outlet>
14</main>
15
16<footer>
17 <p>© 2025 Angular路由示例</p>
18</footer>

1.2 路由配置选项

Angular路由支持多种配置选项:

typescript
1const routes: Routes = [
2 {
3 path: 'products',
4 component: ProductListComponent,
5 children: [ // 嵌套路由
6 {
7 path: ':id',
8 component: ProductDetailComponent
9 }
10 ],
11 canActivate: [AuthGuard], // 路由守卫
12 data: { title: '产品列表', permissions: ['view-products'] }, // 路由数据
13 resolve: { // 预先解析数据
14 products: ProductsResolver
15 }
16 },
17 {
18 path: 'admin',
19 loadChildren: () => import('./admin/admin.module')
20 .then(m => m.AdminModule) // 懒加载模块
21 }
22];

1.3 Router相关指令

Angular路由系统提供了几个重要的指令:

  1. RouterOutlet: <router-outlet></router-outlet> - 显示匹配的组件
  2. RouterLink: <a routerLink="/path"> - 声明式导航链接
  3. RouterLinkActive: <a routerLinkActive="active"> - 当链接激活时应用的CSS类
  4. RouterLinkActiveOptions: [routerLinkActiveOptions]="{exact: true}" - 配置激活行为

1.4 程序式导航

除了使用指令进行导航外,还可以通过Router服务进行程序式导航:

typescript
1import { Component } from '@angular/core';
2import { Router, ActivatedRoute } from '@angular/router';
3
4@Component({
5 selector: 'app-navigation-demo',
6 template: `
7 <button (click)="goToProducts()">查看产品</button>
8 <button (click)="goToProductDetail(123)">查看产品详情</button>
9 <button (click)="goBack()">返回</button>
10 `
11})
12export class NavigationDemoComponent {
13 constructor(
14 private router: Router,
15 private route: ActivatedRoute
16 ) {}
17
18 goToProducts() {
19 this.router.navigate(['/products']);
20 }
21
22 goToProductDetail(id: number) {
23 // 相对于当前路由的导航
24 this.router.navigate([id], { relativeTo: this.route });
25
26 // 或使用绝对路径
27 // this.router.navigate(['/products', id]);
28 }
29
30 goToProductWithQueryParams(category: string) {
31 this.router.navigate(['/products'], {
32 queryParams: { category: category },
33 queryParamsHandling: 'merge' // 合并现有查询参数
34 });
35 }
36
37 goBack() {
38 window.history.back();
39 }
40}

2. 路由参数与数据传递

2.1 路由参数

Angular路由支持三种参数传递方式:

  1. 路径参数:作为URL路径的一部分
  2. 查询参数:作为URL查询字符串的一部分
  3. 路由数据:在路由配置中定义的静态数据

路径参数示例

app-routing.module.ts
typescript
1const routes: Routes = [
2 {
3 path: 'products/:id',
4 component: ProductDetailComponent
5 }
6];
product-detail.component.ts
typescript
1import { Component, OnInit } from '@angular/core';
2import { ActivatedRoute, ParamMap } from '@angular/router';
3import { switchMap } from 'rxjs/operators';
4import { ProductService } from '../services/product.service';
5import { Observable } from 'rxjs';
6import { Product } from '../models/product.model';
7
8@Component({
9 selector: 'app-product-detail',
10 template: `
11 <div *ngIf="product$ | async as product; else loading">
12 <h2>{{ product.name }}</h2>
13 <p>{{ product.description }}</p>
14 <p>价格: {{ product.price | currency:'CNY' }}</p>
15 </div>
16 <ng-template #loading>加载中...</ng-template>
17 `
18})
19export class ProductDetailComponent implements OnInit {
20 product$: Observable<Product>;
21
22 constructor(
23 private route: ActivatedRoute,
24 private productService: ProductService
25 ) {}
26
27 ngOnInit() {
28 // 方式1:使用快照(仅适用于组件不会重用的情况)
29 // const id = this.route.snapshot.paramMap.get('id');
30 // this.product$ = this.productService.getProduct(id);
31
32 // 方式2:使用可观察对象(适用于组件重用的情况)
33 this.product$ = this.route.paramMap.pipe(
34 switchMap((params: ParamMap) => {
35 const id = params.get('id');
36 return this.productService.getProduct(id);
37 })
38 );
39 }
40}

查询参数示例

product-list.component.ts
typescript
1import { Component, OnInit } from '@angular/core';
2import { ActivatedRoute, Router } from '@angular/router';
3import { ProductService } from '../services/product.service';
4import { Product } from '../models/product.model';
5
6@Component({
7 selector: 'app-product-list',
8 template: `
9 <div class="filters">
10 <button (click)="filterByCategory('electronics')">电子产品</button>
11 <button (click)="filterByCategory('clothing')">服装</button>
12 <button (click)="filterByCategory('books')">图书</button>
13 <button (click)="clearFilters()">清除筛选</button>
14 </div>
15
16 <div class="product-list">
17 <div *ngFor="let product of products" class="product-item">
18 <h3>{{ product.name }}</h3>
19 <p>{{ product.description }}</p>
20 <p>{{ product.price | currency:'CNY' }}</p>
21 <button (click)="viewDetails(product.id)">查看详情</button>
22 </div>
23 </div>
24 `
25})
26export class ProductListComponent implements OnInit {
27 products: Product[] = [];
28
29 constructor(
30 private route: ActivatedRoute,
31 private router: Router,
32 private productService: ProductService
33 ) {}
34
35 ngOnInit() {
36 this.route.queryParams.subscribe(params => {
37 const category = params['category'];
38 if (category) {
39 this.loadProductsByCategory(category);
40 } else {
41 this.loadAllProducts();
42 }
43 });
44 }
45
46 filterByCategory(category: string) {
47 this.router.navigate([], {
48 relativeTo: this.route,
49 queryParams: { category: category },
50 queryParamsHandling: 'merge'
51 });
52 }
53
54 clearFilters() {
55 this.router.navigate([], {
56 relativeTo: this.route,
57 queryParams: {}
58 });
59 }
60
61 viewDetails(id: string) {
62 this.router.navigate(['/products', id]);
63 }
64
65 private loadAllProducts() {
66 this.productService.getProducts().subscribe(products => {
67 this.products = products;
68 });
69 }
70
71 private loadProductsByCategory(category: string) {
72 this.productService.getProductsByCategory(category).subscribe(products => {
73 this.products = products;
74 });
75 }
76}

路由数据示例

app-routing.module.ts
typescript
1const routes: Routes = [
2 {
3 path: 'products',
4 component: ProductListComponent,
5 data: {
6 title: '产品列表',
7 breadcrumb: '产品',
8 permissions: ['view-products']
9 }
10 }
11];
product-list.component.ts
typescript
1ngOnInit() {
2 // 访问静态路由数据
3 this.route.data.subscribe(data => {
4 this.pageTitle = data['title'];
5 document.title = this.pageTitle; // 更新页面标题
6
7 // 检查权限
8 const permissions = data['permissions'];
9 this.canEdit = this.authService.hasPermissions(permissions);
10 });
11}

2.2 路由解析器(Resolver)

路由解析器允许在激活路由之前获取数据,避免在组件加载后显示空白状态或加载指示器:

product-resolver.service.ts
typescript
1import { Injectable } from '@angular/core';
2import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
3import { Observable, of } from 'rxjs';
4import { catchError } from 'rxjs/operators';
5import { ProductService } from './product.service';
6import { Product } from '../models/product.model';
7
8@Injectable({
9 providedIn: 'root'
10})
11export class ProductResolver implements Resolve<Product> {
12 constructor(private productService: ProductService) {}
13
14 resolve(
15 route: ActivatedRouteSnapshot,
16 state: RouterStateSnapshot
17 ): Observable<Product> {
18 const id = route.paramMap.get('id');
19 return this.productService.getProduct(id).pipe(
20 catchError(error => {
21 console.error('产品数据获取失败', error);
22 // 返回默认产品或空对象
23 return of({ id: null, name: '未找到产品', description: '', price: 0 });
24 })
25 );
26 }
27}
app-routing.module.ts
typescript
1const routes: Routes = [
2 {
3 path: 'products/:id',
4 component: ProductDetailComponent,
5 resolve: {
6 product: ProductResolver
7 }
8 }
9];
product-detail.component.ts
typescript
1ngOnInit() {
2 // 使用解析器提供的数据,无需等待
3 this.route.data.subscribe(data => {
4 this.product = data['product'];
5 });
6}

3. 嵌套路由与子路由

3.1 嵌套路由配置

嵌套路由允许创建更复杂的应用程序结构,其中子路由与父路由相关:

app-routing.module.ts
typescript
1const routes: Routes = [
2 {
3 path: 'admin',
4 component: AdminComponent,
5 children: [
6 { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
7 { path: 'dashboard', component: AdminDashboardComponent },
8 { path: 'users', component: UserManagementComponent },
9 { path: 'products', component: ProductManagementComponent }
10 ]
11 }
12];
admin.component.html
html
1<div class="admin-layout">
2 <aside class="sidebar">
3 <nav>
4 <ul>
5 <li><a routerLink="/admin/dashboard" routerLinkActive="active">仪表盘</a></li>
6 <li><a routerLink="/admin/users" routerLinkActive="active">用户管理</a></li>
7 <li><a routerLink="/admin/products" routerLinkActive="active">产品管理</a></li>
8 </ul>
9 </nav>
10 </aside>
11
12 <main class="content">
13 <!-- 子路由将在这里渲染 -->
14 <router-outlet></router-outlet>
15 </main>
16</div>

3.2 命名路由出口

Angular支持多个命名的路由出口,允许在同一视图中同时显示多个路由组件:

app-routing.module.ts
typescript
1const routes: Routes = [
2 {
3 path: 'workspace',
4 component: WorkspaceComponent,
5 children: [
6 {
7 path: ':id',
8 component: WorkspaceContentComponent,
9 children: [
10 { path: '', component: WorkspaceDetailComponent, outlet: 'detail' },
11 { path: '', component: WorkspacePropertiesComponent, outlet: 'properties' }
12 ]
13 }
14 ]
15 }
16];
workspace.component.html
html
1<div class="workspace-layout">
2 <aside class="sidebar">
3 <app-workspace-list></app-workspace-list>
4 </aside>
5
6 <main class="main-content">
7 <!-- 主路由出口 -->
8 <router-outlet></router-outlet>
9 </main>
10</div>
workspace-content.component.html
html
1<div class="content-container">
2 <div class="detail-panel">
3 <!-- 命名为"detail"的路由出口 -->
4 <router-outlet name="detail"></router-outlet>
5 </div>
6
7 <div class="properties-panel">
8 <!-- 命名为"properties"的路由出口 -->
9 <router-outlet name="properties"></router-outlet>
10 </div>
11</div>

3.3 相对导航

在嵌套路由结构中,相对导航非常有用:

typescript
1// 导航到当前路由的子路由
2this.router.navigate(['child'], { relativeTo: this.route });
3
4// 导航到父路由
5this.router.navigate(['../'], { relativeTo: this.route });
6
7// 导航到兄弟路由
8this.router.navigate(['../sibling'], { relativeTo: this.route });

4. 路由守卫

路由守卫是拦截导航过程并决定是否允许导航继续的服务。Angular提供了多种类型的守卫:

4.1 CanActivate守卫

控制是否可以访问路由:

auth.guard.ts
typescript
1import { Injectable } from '@angular/core';
2import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
3import { Observable } from 'rxjs';
4import { AuthService } from './auth.service';
5
6@Injectable({
7 providedIn: 'root'
8})
9export class AuthGuard implements CanActivate {
10 constructor(private authService: AuthService, private router: Router) {}
11
12 canActivate(
13 route: ActivatedRouteSnapshot,
14 state: RouterStateSnapshot
15 ): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
16 if (this.authService.isLoggedIn()) {
17 // 检查特定权限
18 const requiredPermissions = route.data['permissions'] as string[];
19 if (requiredPermissions && !this.authService.hasPermissions(requiredPermissions)) {
20 // 重定向到权限不足页面
21 return this.router.createUrlTree(['/forbidden']);
22 }
23
24 return true;
25 }
26
27 // 保存原始URL,登录后重定向回来
28 return this.router.createUrlTree(['/login'], {
29 queryParams: { returnUrl: state.url }
30 });
31 }
32}

4.2 CanActivateChild守卫

控制是否可以访问子路由:

admin.guard.ts
typescript
1import { Injectable } from '@angular/core';
2import { CanActivateChild, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
3import { Observable } from 'rxjs';
4import { AuthService } from './auth.service';
5
6@Injectable({
7 providedIn: 'root'
8})
9export class AdminGuard implements CanActivateChild {
10 constructor(private authService: AuthService, private router: Router) {}
11
12 canActivateChild(
13 childRoute: ActivatedRouteSnapshot,
14 state: RouterStateSnapshot
15 ): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
16 if (this.authService.isAdmin()) {
17 return true;
18 }
19
20 return this.router.createUrlTree(['/forbidden']);
21 }
22}

4.3 CanDeactivate守卫

控制是否可以离开当前路由,通常用于防止用户意外丢失未保存的更改:

can-deactivate.guard.ts
typescript
1import { Injectable } from '@angular/core';
2import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
3import { Observable } from 'rxjs';
4
5export interface CanComponentDeactivate {
6 canDeactivate: () => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
7}
8
9@Injectable({
10 providedIn: 'root'
11})
12export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
13 canDeactivate(
14 component: CanComponentDeactivate,
15 currentRoute: ActivatedRouteSnapshot,
16 currentState: RouterStateSnapshot,
17 nextState?: RouterStateSnapshot
18 ): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
19 return component.canDeactivate ? component.canDeactivate() : true;
20 }
21}
product-edit.component.ts
typescript
1import { Component, OnInit } from '@angular/core';
2import { FormBuilder, FormGroup, Validators } from '@angular/forms';
3import { CanComponentDeactivate } from '../can-deactivate.guard';
4import { Observable } from 'rxjs';
5
6@Component({
7 selector: 'app-product-edit',
8 templateUrl: './product-edit.component.html'
9})
10export class ProductEditComponent implements OnInit, CanComponentDeactivate {
11 productForm: FormGroup;
12 originalProduct: any;
13 hasUnsavedChanges = false;
14
15 constructor(private fb: FormBuilder) {}
16
17 ngOnInit() {
18 this.productForm = this.fb.group({
19 name: ['', Validators.required],
20 price: [0, [Validators.required, Validators.min(0)]],
21 description: ['']
22 });
23
24 // 监听表单更改
25 this.productForm.valueChanges.subscribe(() => {
26 this.hasUnsavedChanges = this.productForm.dirty;
27 });
28
29 // 获取原始产品数据...
30 }
31
32 // 实现CanComponentDeactivate接口
33 canDeactivate(): Observable<boolean> | Promise<boolean> | boolean {
34 if (!this.hasUnsavedChanges) {
35 return true;
36 }
37
38 // 弹出确认对话框
39 return window.confirm('您有未保存的更改,确定要离开吗?');
40 }
41
42 saveChanges() {
43 // 保存表单数据...
44 this.hasUnsavedChanges = false;
45 }
46}

4.4 CanLoad守卫

控制是否可以加载延迟加载模块:

auth.guard.ts
typescript
1import { Injectable } from '@angular/core';
2import { CanLoad, Route, UrlSegment, Router } from '@angular/router';
3import { Observable } from 'rxjs';
4import { AuthService } from './auth.service';
5
6@Injectable({
7 providedIn: 'root'
8})
9export class AuthGuard implements CanLoad {
10 constructor(private authService: AuthService, private router: Router) {}
11
12 canLoad(
13 route: Route,
14 segments: UrlSegment[]
15 ): Observable<boolean> | Promise<boolean> | boolean {
16 if (this.authService.isLoggedIn()) {
17 return true;
18 }
19
20 // 未登录,导航到登录页面
21 this.router.navigate(['/login']);
22 return false;
23 }
24}

4.5 Resolve守卫

前面已经介绍过,用于在路由激活前预先获取数据。

5. 延迟加载

延迟加载是Angular路由系统的一个强大特性,它允许将应用程序拆分成多个模块,并在需要时按需加载,这可以显著提高初始加载性能。

5.1 配置延迟加载模块

app-routing.module.ts
typescript
1const routes: Routes = [
2 { path: '', component: HomeComponent },
3 {
4 path: 'products',
5 loadChildren: () => import('./products/products.module').then(m => m.ProductsModule),
6 canLoad: [AuthGuard]
7 },
8 {
9 path: 'admin',
10 loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
11 canLoad: [AdminGuard]
12 }
13];

5.2 延迟加载模块配置

每个延迟加载的模块必须有自己的路由配置:

products/products-routing.module.ts
typescript
1import { NgModule } from '@angular/core';
2import { RouterModule, Routes } from '@angular/router';
3
4import { ProductListComponent } from './product-list.component';
5import { ProductDetailComponent } from './product-detail.component';
6import { ProductEditComponent } from './product-edit.component';
7import { ProductResolver } from './product.resolver';
8import { CanDeactivateGuard } from '../can-deactivate.guard';
9
10const routes: Routes = [
11 { path: '', component: ProductListComponent },
12 {
13 path: ':id',
14 component: ProductDetailComponent,
15 resolve: { product: ProductResolver }
16 },
17 {
18 path: ':id/edit',
19 component: ProductEditComponent,
20 canDeactivate: [CanDeactivateGuard]
21 }
22];
23
24@NgModule({
25 imports: [RouterModule.forChild(routes)],
26 exports: [RouterModule]
27})
28export class ProductsRoutingModule { }
products/products.module.ts
typescript
1import { NgModule } from '@angular/core';
2import { CommonModule } from '@angular/common';
3import { ReactiveFormsModule } from '@angular/forms';
4
5import { ProductsRoutingModule } from './products-routing.module';
6import { ProductListComponent } from './product-list.component';
7import { ProductDetailComponent } from './product-detail.component';
8import { ProductEditComponent } from './product-edit.component';
9
10@NgModule({
11 declarations: [
12 ProductListComponent,
13 ProductDetailComponent,
14 ProductEditComponent
15 ],
16 imports: [
17 CommonModule,
18 ReactiveFormsModule,
19 ProductsRoutingModule
20 ]
21})
22export class ProductsModule { }

5.3 预加载策略

Angular路由系统支持多种预加载策略:

app-routing.module.ts
typescript
1import { PreloadAllModules, NoPreloading } from '@angular/router';
2
3@NgModule({
4 imports: [
5 RouterModule.forRoot(routes, {
6 preloadingStrategy: PreloadAllModules, // 预加载所有延迟加载模块
7 // 或者不预加载
8 // preloadingStrategy: NoPreloading
9 })
10 ],
11 exports: [RouterModule]
12})
13export class AppRoutingModule { }

也可以创建自定义的预加载策略:

selective-preloading-strategy.service.ts
typescript
1import { Injectable } from '@angular/core';
2import { PreloadingStrategy, Route } from '@angular/router';
3import { Observable, of } from 'rxjs';
4
5@Injectable({
6 providedIn: 'root'
7})
8export class SelectivePreloadingStrategy implements PreloadingStrategy {
9 preload(route: Route, load: () => Observable<any>): Observable<any> {
10 if (route.data && route.data['preload']) {
11 return load();
12 }
13 return of(null);
14 }
15}
app-routing.module.ts
typescript
1const routes: Routes = [
2 {
3 path: 'products',
4 loadChildren: () => import('./products/products.module').then(m => m.ProductsModule),
5 data: { preload: true } // 标记此模块进行预加载
6 },
7 {
8 path: 'admin',
9 loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
10 // 不预加载
11 }
12];
13
14@NgModule({
15 imports: [
16 RouterModule.forRoot(routes, {
17 preloadingStrategy: SelectivePreloadingStrategy
18 })
19 ],
20 exports: [RouterModule]
21})
22export class AppRoutingModule { }

6. 路由事件与生命周期

Angular路由系统触发一系列的事件,可用于跟踪路由过程并实现加载指示器或分析跟踪:

app.component.ts
typescript
1import { Component, OnInit } from '@angular/core';
2import { Router, NavigationStart, NavigationEnd, NavigationCancel, NavigationError, Event } from '@angular/router';
3import { filter } from 'rxjs/operators';
4
5@Component({
6 selector: 'app-root',
7 template: `
8 <div class="loading-indicator" *ngIf="loading">加载中...</div>
9 <router-outlet></router-outlet>
10 `
11})
12export class AppComponent implements OnInit {
13 loading = false;
14
15 constructor(private router: Router) {}
16
17 ngOnInit() {
18 this.router.events.subscribe((event: Event) => {
19 switch (true) {
20 case event instanceof NavigationStart:
21 this.loading = true;
22 break;
23
24 case event instanceof NavigationEnd:
25 case event instanceof NavigationCancel:
26 case event instanceof NavigationError:
27 this.loading = false;
28 break;
29 }
30 });
31
32 // 页面标题更新
33 this.router.events.pipe(
34 filter(event => event instanceof NavigationEnd)
35 ).subscribe(() => {
36 const rt = this.getChild(this.router.routerState.root);
37 rt.data.subscribe(data => {
38 // 更新页面标题
39 if (data.title) {
40 document.title = `${data.title} - My App`;
41 }
42 });
43 });
44 }
45
46 // 递归获取活动路由
47 private getChild(activatedRoute: ActivatedRoute): ActivatedRoute {
48 if (activatedRoute.firstChild) {
49 return this.getChild(activatedRoute.firstChild);
50 } else {
51 return activatedRoute;
52 }
53 }
54}

7. 路由最佳实践

7.1 路由组织模式

对于大型应用,建议采用以下路由组织模式:

  1. 模块化路由:每个功能模块有自己的路由配置
  2. 基于特性的组织:按特性或业务领域组织路由
  3. 懒加载与预加载:合理使用懒加载和预加载策略

7.2 路由命名约定

使用一致的路由命名约定有助于提高可维护性:

  1. 使用kebab-case:例如/user-profile而不是/userProfile
  2. 资源标识:对于RESTful风格的路由,使用/resources/:id/action格式
  3. 避免动词:使用名词命名路由,例如/products而不是/get-products

7.3 路由安全

实施多层路由安全策略:

  1. 认证守卫:验证用户是否已登录
  2. 授权守卫:验证用户是否有权访问特定资源
  3. 数据验证:在服务器端和客户端验证路由参数
  4. CSRF保护:防止跨站请求伪造攻击

7.4 路由状态管理

有效管理路由状态:

  1. 使用查询参数:存储过滤、排序、分页等UI状态
  2. 路由参数序列化:对复杂的查询参数进行序列化
  3. 保持URL清晰:URL应该是可读的,可分享的,可书签的

7.5 路由性能优化

提高路由性能:

  1. 延迟加载:按需加载模块
  2. 路由预加载:在空闲时预加载常用模块
  3. 数据缓存:缓存路由解析器获取的数据
  4. Route Reuse策略:实现自定义路由复用策略,避免不必要的组件重建
custom-route-reuse-strategy.ts
typescript
1import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router';
2
3export class CustomRouteReuseStrategy implements RouteReuseStrategy {
4 private storedRoutes = new Map<string, DetachedRouteHandle>();
5
6 // 确定是否复用路由
7 shouldDetach(route: ActivatedRouteSnapshot): boolean {
8 // 只存储标记为可复用的路由
9 return route.data && route.data['reuseRoute'];
10 }
11
12 // 存储分离的路由
13 store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
14 const id = this.getRouteId(route);
15 this.storedRoutes.set(id, handle);
16 }
17
18 // 确定是否有存储的路由
19 shouldAttach(route: ActivatedRouteSnapshot): boolean {
20 const id = this.getRouteId(route);
21 return this.storedRoutes.has(id);
22 }
23
24 // 恢复存储的路由
25 retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
26 const id = this.getRouteId(route);
27 if (!this.storedRoutes.has(id)) return null;
28 return this.storedRoutes.get(id);
29 }
30
31 // 确定是否应该复用路由
32 shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
33 // 默认的复用策略
34 return future.routeConfig === curr.routeConfig;
35 }
36
37 // 获取路由ID
38 private getRouteId(route: ActivatedRouteSnapshot): string {
39 // 创建一个唯一的路由标识符,考虑路径和参数
40 const path = route.routeConfig ? route.routeConfig.path : '';
41 const id = path || '';
42
43 // 如果有路由参数,将其添加到ID中
44 if (route.params) {
45 Object.keys(route.params).forEach(key => {
46 id += `|${key}=${route.params[key]}`;
47 });
48 }
49
50 return id;
51 }
52}
app.module.ts
typescript
1import { BrowserModule } from '@angular/platform-browser';
2import { NgModule } from '@angular/core';
3import { RouteReuseStrategy } from '@angular/router';
4import { AppRoutingModule } from './app-routing.module';
5import { AppComponent } from './app.component';
6import { CustomRouteReuseStrategy } from './custom-route-reuse-strategy';
7
8@NgModule({
9 declarations: [AppComponent],
10 imports: [BrowserModule, AppRoutingModule],
11 providers: [
12 { provide: RouteReuseStrategy, useClass: CustomRouteReuseStrategy }
13 ],
14 bootstrap: [AppComponent]
15})
16export class AppModule { }

参与讨论