You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular RouteReuseStrategy含动态参数路由复用失效问题排查

自定义RouteReuseStrategy路由复用异常排查

需求说明

  • 支持创建多个RtgViewComponent实例与单个RtgCreationComponent实例,以类浏览器标签页的步进器形式打开,各实例携带动态参数
  • 从RtgViewComponent或AppComponent返回RtgCreationComponent时,需恢复之前的表单数据

相关代码

router-strategy.ts(原代码)

import {RouteReuseStrategy,ActivatedRouteSnapshot,DetachedRouteHandle,} from '@angular/router';

export class RtgRouteReuseStrategy implements RouteReuseStrategy {
  private routeLeftFrom: string | undefined;
  private handlers: { [key: string]: DetachedRouteHandle } = {};

  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    this.routeLeftFrom = route.routeConfig!.path;
    return route.data['shouldReuseRoute'] || false;
  }

  store(route: ActivatedRouteSnapshot, handler: DetachedRouteHandle): void {
    if (handler) {
      this.handlers[this.getUrl(route)] = handler;
    }
  }

  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    const url = this.getUrl(route);
    return !!this.handlers[url];
  }

  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
    if (!route.routeConfig || route.routeConfig.loadChildren) {
      return null;
    }

    return this.handlers[this.getUrl(route)];
  }

  shouldReuseRoute(future: ActivatedRouteSnapshot,current: ActivatedRouteSnapshot): boolean {
    const reUseUrl = future.routeConfig?.data?.['reuse'];
    const defaultReuse = future.routeConfig === current.routeConfig;
    return reUseUrl || defaultReuse;
  }

  getUrl(route: ActivatedRouteSnapshot): string {
    const url = route.url.map((segment) => segment.path).join('/');
    return url;
  }
}

app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AppComponent } from './app.component';
import { AuthGuard } from './core/authguard/auth.guard';
import { RtgCreationComponent } from './modules/rtg-form/components/rtg-creation/rtg-creation.component';
import { RtgViewComponent } from './modules/rtg-form/components/rtg-view/rtg-view.component';

const routes: Routes = [
  { path: '', redirectTo: "rtgmaster", pathMatch: "full" },
  {
    path: "rtgmaster",
    canActivate: [AuthGuard],
    component: AppComponent
  },
  {
    path:"form/:rtgId/:email/:phnNumber",
    canActivate: [AuthGuard],
    component:RtgCreationComponent,
     data: {
        shouldReuseRoute: true,
        reuseRoutesFrom: ["rtgmaster","form/:rtgId/:email/:phnNumber/view"]
     }
  },
  {
    path: "form/:rtgId/:email/:phnNumber/view",
    canActivate: [AuthGuard],
    component:RtgViewComponent
  },
];

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

当前问题

  1. 先创建RtgCreationComponent再创建多个RtgViewComponent,从RtgViewComponent/AppComponent返回时,表单数据与路由实例可正常保留
  2. 先创建RtgViewComponent再创建RtgCreationComponent,从RtgViewComponent返回RtgCreationComponent时,路由实例被破坏(表单数据丢失),但从AppComponent返回正常

问题排查与修复

原代码核心问题

  1. 路由Key生成不完整:仅使用当前路由的路径段,未考虑父路由、路由参数与查询参数,极端场景下会导致不同实例的路由Key冲突
  2. 复用逻辑缺失:未判断目标路由是否已有存储的实例,导致从RtgViewComponent跳转回RtgCreationComponent时,无法复用已存储的实例
  3. 无用变量干扰:routeLeftFrom变量未被使用,属于冗余代码

修复后的router-strategy.ts

import { RouteReuseStrategy, ActivatedRouteSnapshot, DetachedRouteHandle } from '@angular/router';

export class RtgRouteReuseStrategy implements RouteReuseStrategy {
  private handlers: { [key: string]: DetachedRouteHandle } = {};

  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    // 仅存储配置了shouldReuseRoute的路由
    return !!route.data['shouldReuseRoute'];
  }

  store(route: ActivatedRouteSnapshot, handler: DetachedRouteHandle): void {
    if (handler) {
      this.handlers[this.getRouteKey(route)] = handler;
    }
  }

  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    return !!this.handlers[this.getRouteKey(route)];
  }

  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
    if (!route.routeConfig || route.routeConfig.loadChildren) {
      return null;
    }
    return this.handlers[this.getRouteKey(route)] || null;
  }

  shouldReuseRoute(future: ActivatedRouteSnapshot, current: ActivatedRouteSnapshot): boolean {
    // 优先判断目标路由是否已有存储实例,是则直接复用
    if (future.data['shouldReuseRoute'] && this.handlers[this.getRouteKey(future)]) {
      return true;
    }
    // 默认逻辑:同一路由配置才复用
    return future.routeConfig === current.routeConfig;
  }

  // 生成唯一路由Key,包含完整路径、路由参数与查询参数
  private getRouteKey(route: ActivatedRouteSnapshot): string {
    const fullPath = this.getFullPath(route);
    const params = JSON.stringify({ ...route.params, ...route.queryParams });
    return `${fullPath}?${params}`;
  }

  // 拼接包含父路由的完整路径
  private getFullPath(route: ActivatedRouteSnapshot): string {
    let path = '';
    let currentRoute: ActivatedRouteSnapshot | null = route;
    while (currentRoute) {
      if (currentRoute.url.length) {
        path = `${currentRoute.url.map(segment => segment.path).join('/')}/${path}`;
      }
      currentRoute = currentRoute.parent;
    }
    return path.replace(/\/$/, '');
  }
}

修复说明

  1. 完善路由Key生成:通过getFullPath拼接父路由路径,结合路由参数、查询参数生成唯一Key,避免不同实例的路由被混淆
  2. 优化复用逻辑:在shouldReuseRoute中优先判断目标路由是否已有存储实例,确保从任何允许的路由返回时都能复用RtgCreationComponent实例
  3. 简化冗余代码:移除无用的routeLeftFrom变量,精简逻辑

内容的提问来源于stack exchange,提问作者SGR

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 16:04:52