MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Angular路由的配置与优化策略

2023-01-237.7k 阅读

Angular 路由基础概念

在前端应用开发中,路由起着至关重要的作用。它决定了用户在应用中如何导航,以及不同视图之间如何切换。Angular 路由是 Angular 框架中用于实现单页应用(SPA)导航功能的模块。通过合理配置路由,开发者可以创建出具有良好用户体验、高效的前端应用。

在 Angular 中,路由通过定义一组路径映射到相应的组件来工作。当用户在浏览器地址栏中输入一个 URL,或者点击应用内的链接时,Angular 路由会根据配置的规则匹配 URL,并加载对应的组件。这使得应用在不进行完整页面刷新的情况下更新视图,大大提升了用户体验。

路由的基本配置

1. 安装和导入路由模块

首先,需要确保项目中安装了 @angular/router 模块。如果是使用 Angular CLI 创建的项目,该模块会默认安装。在 app.module.ts 文件中,导入 RouterModule

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';

@NgModule({
  imports: [
    BrowserModule,
    RouterModule
  ],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule {}

2. 定义路由配置对象

在项目中创建一个新的文件,例如 app - routing.module.ts,用于定义路由配置。路由配置是一个数组,每个元素是一个对象,包含 path(路径)和 component(组件)属性。

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';

const routes: Routes = [
  { path: 'home', component: HomeComponent },
  { path: 'about', component: AboutComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

在上述代码中,Routes 是一个类型,定义了路由配置数组的结构。每个对象中的 path 表示 URL 路径,component 表示该路径对应的组件。RouterModule.forRoot(routes) 方法用于配置应用的根路由。

3. 在模板中使用路由链接

在模板中,可以使用 routerLink 指令来创建导航链接。例如,在 app.component.html 中:

<ul>
  <li><a routerLink="/home">Home</a></li>
  <li><a routerLink="/about">About</a></li>
</ul>
<router - outlet></router - outlet>

router - outlet 是一个占位符,用于显示当前路由对应的组件内容。当用户点击链接时,Angular 会根据 routerLink 的值匹配路由,并将对应的组件渲染到 router - outlet 位置。

路由参数

1. 静态路由参数

有时候,我们需要在路由中传递一些固定的参数。例如,我们有一个产品详情页面,每个产品有不同的 ID,但页面结构相同。可以通过在路由配置中定义参数来实现。 首先,在 app - routing.module.ts 中定义带有参数的路由:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProductDetailComponent } from './product - detail/product - detail.component';

const routes: Routes = [
  { path: 'product/:id', component: ProductDetailComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

在上述代码中,:id 是一个参数占位符。在 ProductDetailComponent 中,可以通过 ActivatedRoute 服务来获取这个参数:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app - product - detail',
  templateUrl: './product - detail.component.html',
  styleUrls: ['./product - detail.component.css']
})
export class ProductDetailComponent implements OnInit {
  productId: string;

  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    this.productId = this.route.snapshot.paramMap.get('id');
  }
}

在模板中,可以显示这个参数值:

<div>
  <h2>Product Detail - {{productId}}</h2>
</div>

2. 动态路由参数

除了静态参数,有时参数值需要根据用户操作动态变化。例如,用户在一个列表中选择不同的产品,产品 ID 会改变。这时,可以使用 subscribe 方法来监听参数的变化。

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app - product - detail',
  templateUrl: './product - detail.component.html',
  styleUrls: ['./product - detail.component.css']
})
export class ProductDetailComponent implements OnInit {
  productId: string;

  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    this.route.paramMap.subscribe(params => {
      this.productId = params.get('id');
    });
  }
}

这样,当 URL 中的参数值发生变化时,ProductDetailComponent 中的 productId 也会相应更新。

路由守卫

路由守卫是 Angular 路由中的一个强大功能,它允许我们在导航到某个路由之前、之后,或者离开某个路由时执行一些逻辑。这对于实现权限控制、数据预取等功能非常有用。

1. CanActivate 守卫

CanActivate 守卫用于决定是否可以激活某个路由。例如,我们有一个管理页面,只有登录用户才能访问。可以创建一个 AuthGuard 来实现这个功能。 首先,创建 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(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): boolean {
    if (this.authService.isLoggedIn()) {
      return true;
    } else {
      this.router.navigate(['/login']);
      return false;
    }
  }
}

在上述代码中,AuthGuard 实现了 CanActivate 接口的 canActivate 方法。如果用户已登录(通过 AuthServiceisLoggedIn 方法判断),则允许导航到目标路由;否则,导航到登录页面。

然后,在路由配置中使用这个守卫:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AdminComponent } from './admin/admin.component';
import { AuthGuard } from './auth - guard';

const routes: Routes = [
  { path: 'admin', component: AdminComponent, canActivate: [AuthGuard] }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

2. CanDeactivate 守卫

CanDeactivate 守卫用于决定是否可以离开某个路由。例如,用户在填写表单时,如果未保存数据就试图离开页面,我们可以提示用户保存数据。 创建 form - can - deactivate - guard.ts 文件:

import { Injectable } from '@angular/core';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { FormComponent } from './form.component';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class FormCanDeactivateGuard implements CanDeactivate<FormComponent> {
  canDeactivate(
    component: FormComponent,
    currentRoute: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot,
    nextState?: RouterStateSnapshot
  ): Observable<boolean> | Promise<boolean> | boolean {
    return component.canDeactivate();
  }
}

FormComponent 中,实现 canDeactivate 方法:

import { Component } from '@angular/core';

@Component({
  selector: 'app - form',
  templateUrl: './form.component.html',
  styleUrls: ['./form.component.css']
})
export class FormComponent {
  isFormDirty = false;

  canDeactivate(): boolean {
    if (this.isFormDirty) {
      return window.confirm('You have unsaved changes. Do you really want to leave?');
    }
    return true;
  }
}

在路由配置中使用这个守卫:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { FormComponent } from './form.component';
import { FormCanDeactivateGuard } from './form - can - deactivate - guard';

const routes: Routes = [
  { path: 'form', component: FormComponent, canDeactivate: [FormCanDeactivateGuard] }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

3. Resolve 守卫

Resolve 守卫用于在路由激活之前预取数据。例如,在产品详情页面,我们希望在页面加载之前就获取产品的详细信息。 创建 product - resolver.ts 文件:

import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { ProductService } from './product.service';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class ProductResolver implements Resolve<any> {
  constructor(private productService: ProductService) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<any> | Promise<any> | any {
    const productId = route.paramMap.get('id');
    return this.productService.getProduct(productId);
  }
}

在路由配置中使用这个守卫:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProductDetailComponent } from './product - detail.component';
import { ProductResolver } from './product - resolver';

const routes: Routes = [
  { path: 'product/:id', component: ProductDetailComponent, resolve: { product: ProductResolver } }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

ProductDetailComponent 中,可以通过 ActivatedRoute 获取预取的数据:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app - product - detail',
  templateUrl: './product - detail.component.html',
  styleUrls: ['./product - detail.component.css']
})
export class ProductDetailComponent implements OnInit {
  product;

  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    this.product = this.route.snapshot.data['product'];
  }
}

路由配置的优化策略

1. 路由懒加载

随着应用规模的增大,初始加载的 JavaScript 代码量也会增加,导致应用启动缓慢。路由懒加载可以解决这个问题。它允许我们将应用的部分模块在需要时才加载,而不是在应用启动时全部加载。 首先,创建一个需要懒加载的模块,例如 admin - module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AdminComponent } from './admin.component';

@NgModule({
  imports: [CommonModule],
  declarations: [AdminComponent]
})
export class AdminModule {}

然后,在路由配置中使用懒加载:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  { path: 'admin', loadChildren: () => import('./admin - module').then(m => m.AdminModule) }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

在上述代码中,loadChildren 属性使用了动态导入(import()),当用户导航到 /admin 路径时,才会加载 AdminModule。这样可以显著减少应用的初始加载时间。

2. 优化路由匹配策略

Angular 路由支持多种匹配策略,合理选择匹配策略可以提高路由匹配的效率。

  • 精确匹配:默认情况下,路由是精确匹配的。例如,{ path: 'home', component: HomeComponent } 只会匹配 /home 路径,不会匹配 /home/sub - path
  • 前缀匹配:可以使用 pathMatch: 'prefix' 来实现前缀匹配。例如:
const routes: Routes = [
  { path: 'home', component: HomeComponent, pathMatch: 'prefix' }
];

这样,/home/home/sub - path 等路径都会匹配到 HomeComponent。但要注意,前缀匹配可能会导致一些意外的匹配,使用时需谨慎。

3. 合并相似路由

如果有多个路由指向相同的组件,只是参数不同,可以考虑合并这些路由。例如,有两个路由 { path: 'product/list', component: ProductListComponent }{ path: 'product/list/:category', component: ProductListComponent },可以合并为:

const routes: Routes = [
  { path: 'product/list/:category?', component: ProductListComponent }
];

其中,:category? 表示 category 参数是可选的。这样可以减少路由配置的冗余,提高代码的可维护性。

4. 合理使用通配符路由

通配符路由({ path: '**', component: PageNotFoundComponent })用于匹配所有未定义的路由。虽然它很方便处理 404 页面,但应该放在路由配置的最后,避免影响其他正常路由的匹配。同时,在使用通配符路由时,要注意性能问题,因为它会匹配所有路径,可能会导致不必要的组件加载。

路由动画

路由动画可以为应用添加更加生动的用户体验,使页面切换更加流畅和吸引人。Angular 提供了 @angular/animations 模块来实现路由动画。

1. 安装和导入动画模块

app.module.ts 中导入 BrowserAnimationsModule

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { BrowserAnimationsModule } from '@angular/platform - browser/animations';
import { AppComponent } from './app.component';

@NgModule({
  imports: [
    BrowserModule,
    BrowserAnimationsModule
  ],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule {}

2. 创建动画

在组件的 @Component 装饰器中定义动画。例如,在 app.component.ts 中:

import { Component } from '@angular/core';
import { trigger, transition, style, animate } from '@angular/animations';

@Component({
  selector: 'app - root',
  templateUrl: './app.component.html',
  animations: [
    trigger('routeAnimation', [
      transition('* <=> *', [
        style({ opacity: 0 }),
        animate('300ms ease - in - out', style({ opacity: 1 }))
      ])
    ])
  ]
})
export class AppComponent {}

在上述代码中,使用 trigger 创建了一个名为 routeAnimation 的动画触发器。transition 定义了动画的过渡效果,这里是从任意状态到任意状态的过渡,先将透明度设置为 0,然后在 300 毫秒内以 ease - in - out 的缓动函数将透明度设置为 1。

3. 在模板中应用动画

app.component.html 中,使用 @routeAnimation 来应用动画:

<router - outlet (@routeAnimation)></router - outlet>

这样,当路由切换时,就会应用定义好的动画效果。还可以根据不同的路由过渡定义更复杂的动画,例如淡入淡出、滑动等效果,通过组合不同的 styleanimate 操作来实现。

高级路由配置

1. 嵌套路由

在实际应用中,很多时候页面结构是多层次的,需要使用嵌套路由来实现。例如,一个电商应用可能有产品列表页面,每个产品又有详细信息页面,详细信息页面可能还有评论、规格等子页面。 首先,在父组件的路由配置中定义子路由。假设父组件是 ProductComponent,在 app - routing.module.ts 中:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProductComponent } from './product.component';
import { ProductDetailComponent } from './product - detail.component';
import { ProductReviewsComponent } from './product - reviews.component';

const productRoutes: Routes = [
  { path: 'detail', component: ProductDetailComponent },
  { path:'reviews', component: ProductReviewsComponent }
];

const routes: Routes = [
  { path: 'product', component: ProductComponent, children: productRoutes }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

ProductComponent 的模板中,需要添加一个 router - outlet 来显示子路由的内容:

<div>
  <h2>Product Page</h2>
  <ul>
    <li><a routerLink="detail">Product Detail</a></li>
    <li><a routerLink="reviews">Product Reviews</a></li>
  </ul>
  <router - outlet></router - outlet>
</div>

这样,当用户导航到 /product/detail/product/reviews 时,对应的子组件会在 ProductComponentrouter - outlet 中显示。

2. 辅助路由

辅助路由允许在同一时间显示多个视图。例如,一个应用可能有一个主要内容区域和一个侧边栏,侧边栏的内容可以根据用户操作独立切换,而不影响主要内容区域。 首先,在路由配置中定义辅助路由。假设主要内容路由为 primary,侧边栏路由为 sidebar

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { MainContentComponent } from './main - content.component';
import { SidebarComponent } from './sidebar.component';

const routes: Routes = [
  { path: 'home', component: MainContentComponent, outlet: 'primary' },
  { path: 'sidebar - content', component: SidebarComponent, outlet:'sidebar' }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

在模板中,需要为每个路由出口定义对应的 router - outlet

<router - outlet name="primary"></router - outlet>
<router - outlet name="sidebar"></router - outlet>

然后,可以通过链接来导航到不同的辅助路由:

<a routerLink="/home" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">Home</a>
<a routerLink="/sidebar - content" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">Sidebar Content</a>

这样,主要内容和侧边栏可以独立切换,提供更灵活的用户界面。

通过合理配置和优化 Angular 路由,开发者可以创建出高效、用户体验良好的前端应用。从基础的路由配置到高级的路由功能,再到路由的优化和动画,每个环节都对应用的质量和性能有着重要影响。在实际开发中,应根据项目的需求和特点,灵活运用这些知识,打造出优秀的 Angular 应用。