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

Angular路由的高级配置与应用

2022-01-163.3k 阅读

一、Angular 路由基础回顾

在深入探讨 Angular 路由的高级配置与应用之前,先来简单回顾一下 Angular 路由的基础知识。

Angular 路由是构建单页应用(SPA)的核心机制之一,它允许我们根据不同的 URL 显示不同的组件,从而实现页面之间的导航和状态管理。

1.1 基本路由配置

在 Angular 中,路由配置通常定义在一个专门的路由模块中。例如,假设我们有一个简单的应用,包含 HomeComponentAboutComponent,其基本路由配置如下:

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) 将这些路由配置注册到应用的根模块中。

1.2 路由导航

在模板中,我们可以使用 routerLink 指令来创建导航链接。例如:

<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> 中。

二、高级路由配置

2.1 嵌套路由

嵌套路由允许在一个组件的模板中再包含一个 <router - outlet>,从而实现更复杂的页面布局。假设我们有一个 ProductComponent,它有自己的子路由,用于显示产品的详细信息和评论等。

首先,定义子路由配置:

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

const productRoutes: Routes = [
  {
    path: 'product', component: ProductComponent, children: [
      { path: 'details', component: ProductDetailsComponent },
      { path:'reviews', component: ProductReviewsComponent }
    ]
  }
];

@NgModule({
  imports: [RouterModule.forChild(productRoutes)],
  exports: [RouterModule]
})
export class ProductRoutingModule { }

ProductComponent 的模板中添加 <router - outlet>

<h1>Product Page</h1>
<ul>
  <li><a routerLink="details">Product Details</a></li>
  <li><a routerLink="reviews">Product Reviews</a></li>
</ul>
<router - outlet></router - outlet>

注意,这里的子路由链接使用相对路径,相对于父路由的路径。例如,当访问 /product/details 时,ProductComponentProductDetailsComponent 都会被渲染。

2.2 路由参数

路由参数是在 URL 中传递数据的一种方式。有两种常见的路由参数类型:路径参数和查询参数。

路径参数:假设我们有一个 UserComponent,用于显示特定用户的信息,其路由配置如下:

const userRoutes: Routes = [
  { path: 'user/:id', component: UserComponent }
];

UserComponent 中,可以通过 ActivatedRoute 服务来获取参数:

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

@Component({
  selector: 'app - user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.css']
})
export class UserComponent implements OnInit {
  userId: string;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.route.params.subscribe(params => {
      this.userId = params['id'];
    });
  }
}

这样,当访问 /user/123 时,UserComponent 就能获取到 id123

查询参数:查询参数通常用于传递可选的过滤条件等。例如,我们有一个 SearchComponent,用于根据关键词搜索商品:

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

@Component({
  selector: 'app - search',
  templateUrl: './search.component.html',
  styleUrls: ['./search.component.css']
})
export class SearchComponent implements OnInit {
  keyword: string;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.route.queryParams.subscribe(params => {
      this.keyword = params['keyword'];
    });
  }
}

在导航到搜索页面时,可以使用 routerLink 传递查询参数:

<a [routerLink]="['/search']" [queryParams]="{keyword: 'angular' }">Search Angular</a>

2.3 路由守卫

路由守卫用于在导航发生之前或之后执行一些逻辑,例如验证用户是否登录、权限检查等。常见的路由守卫有 CanActivateCanDeactivateResolve 等。

CanActivate 守卫:假设我们有一个需要用户登录才能访问的 DashboardComponent,可以创建一个 AuthGuard

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 {
      this.router.navigate(['/login']);
      return false;
    }
  }
}

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

const appRoutes: Routes = [
  { path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] },
  { path: 'login', component: LoginComponent }
];

这样,当用户尝试访问 /dashboard 时,如果未登录,就会被重定向到 /login

CanDeactivate 守卫:当用户离开某个组件时,CanDeactivate 守卫可以用于提示用户是否保存未保存的更改等。假设我们有一个 EditComponent,用户在编辑内容后可能未保存就离开页面,创建一个 CanDeactivateGuard

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

@Injectable({
  providedIn: 'root'
})
export class CanDeactivateGuard implements CanDeactivate<EditComponent> {
  canDeactivate(component: EditComponent, route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    return component.canDeactivate();
  }
}

EditComponent 中定义 canDeactivate 方法:

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

@Component({
  selector: 'app - edit',
  templateUrl: './edit.component.html',
  styleUrls: ['./edit.component.css']
})
export class EditComponent implements OnInit {
  isDirty = false;

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

  ngOnInit() { }
}

在路由配置中使用该守卫:

const editRoutes: Routes = [
  { path: 'edit', component: EditComponent, canDeactivate: [CanDeactivateGuard] }
];

Resolve 守卫Resolve 守卫用于在路由激活之前获取数据,确保组件在渲染时所需的数据已经准备好。假设我们有一个 PostComponent,需要在显示之前获取特定文章的数据,创建一个 PostResolver

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

@Injectable({
  providedIn: 'root'
})
export class PostResolver implements Resolve<any> {
  constructor(private postService: PostService) { }

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | any {
    const postId = route.params['id'];
    return this.postService.getPost(postId);
  }
}

在路由配置中使用该解析器:

const postRoutes: Routes = [
  { path: 'post/:id', component: PostComponent, resolve: { post: PostResolver } }
];

PostComponent 中,可以通过 ActivatedRoute 获取解析后的数据:

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

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

  constructor(private route: ActivatedRoute) { }

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

三、高级路由应用

3.1 路由动画

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

首先,在 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';
import { AppRoutingModule } from './app - routing.module';

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

然后,在路由组件的 @Component 装饰器中定义动画:

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

@Component({
  selector: 'app - home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css'],
  animations: [
    trigger('fadeInOut', [
      state('void', style({ opacity: 0 })),
      transition(':enter, :leave', [
        animate(500)
      ])
    ])
  ]
})
export class HomeComponent implements OnInit {
  constructor() { }

  ngOnInit() { }
}

在模板中应用动画:

<div [@fadeInOut]>
  <h1>Home Page</h1>
  <p>Welcome to our application.</p>
</div>

这里定义了一个简单的淡入淡出动画,当组件进入或离开视图时,会有 500 毫秒的动画过渡效果。

3.2 懒加载路由

懒加载路由是一种优化应用加载性能的重要技术。它允许我们在需要时才加载相应的模块,而不是在应用启动时就加载所有模块。

假设我们有一个大型应用,包含多个功能模块,如用户模块、订单模块等。可以将这些模块设置为懒加载。

首先,创建一个独立的模块,例如 UserModule

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UserComponent } from './user/user.component';
import { UserRoutingModule } from './user - routing.module';

@NgModule({
  declarations: [UserComponent],
  imports: [CommonModule, UserRoutingModule]
})
export class UserModule { }

然后,在 UserRoutingModule 中定义路由:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { UserComponent } from './user/user.component';

const userRoutes: Routes = [
  { path: 'user', component: UserComponent }
];

@NgModule({
  imports: [RouterModule.forChild(userRoutes)],
  exports: [RouterModule]
})
export class UserRoutingModule { }

在主路由模块 app - routing.module.ts 中配置懒加载:

const appRoutes: Routes = [
  {
    path: 'users',
    loadChildren: () => import('./user/user.module').then(m => m.UserModule)
  }
];

这样,当用户访问 /users 路径时,UserModule 才会被加载,从而提高了应用的初始加载速度。

3.3 自定义路由策略

在某些情况下,默认的路由匹配策略可能无法满足需求,这时我们可以自定义路由策略。

Angular 提供了 UrlMatcher 接口来实现自定义路由匹配逻辑。例如,假设我们希望支持更灵活的路径匹配,比如忽略路径中的某些特定字符。

首先,创建一个自定义的 UrlMatcher 函数:

import { UrlSegment, UrlSegmentGroup, Route, UrlMatchResult, UrlTree } from '@angular/router';

export function customUrlMatcher(segments: UrlSegment[], group: UrlSegmentGroup, route: Route): UrlMatchResult | null {
  // 简单示例:忽略路径中的'-'字符
  const cleanSegments = segments.map(segment => segment.path.replace(/-/g, ''));
  const newTree: UrlTree = {
    segments: cleanSegments,
    root: group.root,
    children: group.children,
    parent: group.parent
  };
  return {
    consumed: segments,
    posParams: {},
    // 这里简单返回 true,表示匹配成功,实际应用中可以更复杂的逻辑
    isMatch: true,
    toString() {
      return newTree.toString();
    }
  };
}

然后,在路由配置中使用这个自定义的 UrlMatcher

const customRoutes: Routes = [
  { path: 'custom - path', component: CustomComponent, matcher: customUrlMatcher }
];

这样,当访问类似于 /custom - path - with - dashes 时,自定义的路由策略会忽略 - 字符进行匹配。

四、路由与状态管理

在复杂的单页应用中,路由与状态管理紧密相关。状态管理库如 Redux 或 NgRx 可以与 Angular 路由协同工作,实现更高效的应用状态管理。

4.1 结合 NgRx 进行状态管理

NgRx 是一个基于 Redux 模式的状态管理库,非常适合与 Angular 路由配合使用。

首先,安装 @ngrx/store@ngrx/effects

npm install @ngrx/store @ngrx/effects

假设我们有一个应用,需要根据当前路由状态来管理一些全局状态。例如,在用户进入特定路由时,加载相应的数据。

创建一个 router - effects.ts 文件:

import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { Router } from '@angular/router';
import { map, switchMap } from 'rxjs/operators';
import { loadDataForRoute } from './app.actions';
import { DataService } from './data.service';

@Injectable()
export class RouterEffects {
  loadData$ = createEffect(() =>
    this.actions$.pipe(
      ofType(loadDataForRoute),
      map(action => action.route),
      switchMap(route => {
        if (route === '/specific - route') {
          return this.dataService.getDataForSpecificRoute().pipe(
            map(data => ({ type: '[Data] Loaded for Specific Route', data }))
          );
        }
        return [];
      })
    )
  );

  constructor(private actions$: Actions, private router: Router, private dataService: DataService) { }
}

app.actions.ts 中定义动作:

import { createAction, props } from '@ngrx/store';

export const loadDataForRoute = createAction(
  '[Router] Load Data for Route',
  props<{ route: string }>()
);

app.reducer.ts 中定义相关的 reducer:

import { createReducer, on } from '@ngrx/store';
import { loadDataForRoute } from './app.actions';

export interface AppState {
  data: any;
}

const initialState: AppState = {
  data: null
};

export const appReducer = createReducer(
  initialState,
  on(loadDataForRoute, (state, action) => ({...state })),
  on('[Data] Loaded for Specific Route', (state, action) => ({...state, data: action.data }))
);

app.module.ts 中配置 NgRx:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { appReducer } from './app.reducer';
import { RouterEffects } from './router - effects';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app - routing.module';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    StoreModule.forRoot({ app: appReducer }),
    EffectsModule.forRoot([RouterEffects])
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

在路由守卫或组件中,可以触发 loadDataForRoute 动作:

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { Store } from '@ngrx/store';
import { loadDataForRoute } from './app.actions';

@Injectable({
  providedIn: 'root'
})
export class CustomGuard implements CanActivate {
  constructor(private store: Store, private router: Router) { }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    this.store.dispatch(loadDataForRoute({ route: state.url }));
    return true;
  }
}

这样,通过 NgRx 与路由的结合,可以更好地管理应用在不同路由状态下的数据加载和状态更新。

4.2 路由状态与全局状态同步

除了在特定路由时加载数据,还可以将路由状态与全局状态进行同步。例如,将当前路由路径存储在全局状态中,以便其他组件可以根据路由状态进行相应的显示或操作。

router - effects.ts 中添加同步路由路径到全局状态的逻辑:

import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { Router } from '@angular/router';
import { map } from 'rxjs/operators';
import { setCurrentRoute } from './app.actions';

@Injectable()
export class RouterEffects {
  syncRoute$ = createEffect(() =>
    this.actions$.pipe(
      ofType('@angular/router/NavigationEnd'),
      map(() => this.router.url),
      map(url => setCurrentRoute({ route: url }))
    )
  );

  constructor(private actions$: Actions, private router: Router) { }
}

app.actions.ts 中定义 setCurrentRoute 动作:

import { createAction, props } from '@ngrx/store';

export const setCurrentRoute = createAction(
  '[Router] Set Current Route',
  props<{ route: string }>()
);

app.reducer.ts 中更新 reducer 以处理 setCurrentRoute 动作:

import { createReducer, on } from '@ngrx/store';
import { setCurrentRoute } from './app.actions';

export interface AppState {
  currentRoute: string;
}

const initialState: AppState = {
  currentRoute: ''
};

export const appReducer = createReducer(
  initialState,
  on(setCurrentRoute, (state, action) => ({...state, currentRoute: action.route }))
);

这样,在任何组件中都可以通过访问全局状态来获取当前路由路径,从而实现更灵活的基于路由状态的业务逻辑。

通过以上对 Angular 路由的高级配置与应用的详细介绍,包括嵌套路由、路由参数、路由守卫、路由动画、懒加载、自定义路由策略以及与状态管理的结合等方面,希望能帮助开发者构建出更强大、高效且用户体验良好的单页应用。在实际开发中,应根据具体的业务需求灵活运用这些技术,不断优化应用的性能和功能。