手记

路由复用策略与方法

背景

默认情况下,路由从 A 组件跳转到 B 组件的时候,A 组件的状态也会一并销毁。
但有时候,从 A 组件跳转到 B 组件,再从B 组件切换回 A 组件,我们希望看到A 组件依旧保持原来的状态,表单里面的内容依然存在。
此时,我们就需要 Angular 的路由复用策略,即 RouteReuseStrategy

RouteReuseStrategy 提供的方法

  • shouldDetach:判断是否复用此路由
  • store:储存需要复用的路由
  • shouldAttach:判断是否启用路由缓存
  • retrieve:获取已经储存的路由
  • shouldReuseRoute:判断是否应该复用路由

使用方法和步骤

首先,创建一个服务文件: route-cache.service.ts

内容如下:

import { Injectable } from '@angular/core';
import { RouteReuseStrategy, DefaultUrlSerializer, ActivatedRouteSnapshot, DetachedRouteHandle } from "@angular/router";

interface IRouteConfigData {
  reuse: boolean;
}

interface ICachedRoute {
  handle: DetachedRouteHandle;
  data: IRouteConfigData;
}

@Injectable()
export class RouteCacheService implements RouteReuseStrategy {

  private routeCache = new Map<string, ICachedRoute>();

  shouldReuseRoute(
    future: ActivatedRouteSnapshot,
    curr: ActivatedRouteSnapshot
  ): boolean {
    let ret = future.routeConfig === curr.routeConfig;
    console.log("shouldReuseRoute called", ret);
    if (ret) {
      this.addRedirectsRecursively(future); // update redirects
    }
    return ret;
  }

  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    const data = this.getRouteData(route);
    console.log(
      "shouldDetach check if we want to detach and store route",
      data && data.reuse
    );
    return data && data.reuse;
  }

  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    const url = this.getFullRouteUrl(route);
    const data = this.getRouteData(route);
    this.routeCache.set(url, { handle, data });
    this.addRedirectsRecursively(route);
    //console.log("store route", this.routeCache);
  }

  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    const url = this.getFullRouteUrl(route);
    console.log(
      "shouldAttach if retrive route is true",
      this.routeCache.has(url)
    );
    return this.routeCache.has(url);
  }

  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | any {
    const url = this.getFullRouteUrl(route);
    const data = this.getRouteData(route);
    console.log(
      "retrive route",
      data && data.reuse && this.routeCache.has(url)
    );
    return data && data.reuse && this.routeCache.has(url)
      ? this.routeCache.get(url)?.handle
      : null;
  }

  private addRedirectsRecursively(route: ActivatedRouteSnapshot): void {
    const config = route.routeConfig;
    if (config) {
      if (!config.loadChildren) {
        const routeFirstChild = route.firstChild;
        const routeFirstChildUrl = routeFirstChild
          ? this.getRouteUrlPaths(routeFirstChild).join("/")
          : "";
        const childConfigs = config.children;
        if (childConfigs) {
          const childConfigWithRedirect = childConfigs.find(
            c => c.path === "" && !!c.redirectTo
          );
          if (childConfigWithRedirect) {
            childConfigWithRedirect.redirectTo = routeFirstChildUrl;
          }
        }
      }
      route.children.forEach(childRoute =>
        this.addRedirectsRecursively(childRoute)
      );
    }
  }

  private getFullRouteUrl(route: ActivatedRouteSnapshot): string {
    return this.getFullRouteUrlPaths(route)
      .filter(Boolean)
      .join("/");
  }

  private getFullRouteUrlPaths(route: ActivatedRouteSnapshot): string[] {
    const paths = this.getRouteUrlPaths(route);
    return route.parent
      ? [...this.getFullRouteUrlPaths(route.parent), ...paths]
      : paths;
  }

  private getRouteUrlPaths(route: ActivatedRouteSnapshot): string[] {
    return route.url.map(urlSegment => urlSegment.path);
  }

  private getRouteData(route: ActivatedRouteSnapshot): IRouteConfigData | any {
    return route.routeConfig && (route.routeConfig.data as IRouteConfigData);
  }

}

然后,在根模块里(app.module.ts)引入并注入此服务:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule, Components } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClientModule } from '@angular/common/http';

import { MaterialModule } from '@common/material/material.module';
import { ApiService } from '@common/service/api/api.service';
import { SnackBarService } from '@common/material/snack-bar/snack-bar.service';

// 引入默认的路由复用接口类 RouteReuseStrategy 和我们自定义的服务 
import { RouteReuseStrategy } from "@angular/router";
import { RouteCacheService } from '@common/service/route/route-cache.service';

@NgModule({
  declarations: [
    AppComponent, 
    ...Components
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    HttpClientModule,
    MaterialModule,
  ],
  providers: [ApiService, SnackBarService, 
    
             // 注入 RouteCacheService 服务
             { provide: RouteReuseStrategy, useClass: RouteCacheService }],
  bootstrap: [AppComponent]
})
export class AppModule { }

最后,哪个路由需要复用(根路由和子路由都如此配置),就添加 data: { reuse: true } 即可:

const routes: Routes = [
  { 
    path: '', 
    // 
    data: { reuse: true }, 
    component: InPaymentComponent 
  },
  {
    path: 'update',
    loadChildren: () => import('./modules/update/update.module').then((m) => m.UpdateModule),
  },
  {
    path: 'check',
    loadChildren: () => import('./modules/check/check.module').then((m) => m.CheckModule),
  }
];

注意:RouteReuseStrategy路由复用的方法,不适用于惰性模块加载。

0人推荐
随时随地看视频
慕课网APP