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 {}
当前问题
- 先创建
RtgCreationComponent再创建多个RtgViewComponent,从RtgViewComponent/AppComponent返回时,表单数据与路由实例可正常保留 - 先创建
RtgViewComponent再创建RtgCreationComponent,从RtgViewComponent返回RtgCreationComponent时,路由实例被破坏(表单数据丢失),但从AppComponent返回正常
问题排查与修复
原代码核心问题
- 路由Key生成不完整:仅使用当前路由的路径段,未考虑父路由、路由参数与查询参数,极端场景下会导致不同实例的路由Key冲突
- 复用逻辑缺失:未判断目标路由是否已有存储的实例,导致从
RtgViewComponent跳转回RtgCreationComponent时,无法复用已存储的实例 - 无用变量干扰:
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(/\/$/, ''); } }
修复说明
- 完善路由Key生成:通过
getFullPath拼接父路由路径,结合路由参数、查询参数生成唯一Key,避免不同实例的路由被混淆 - 优化复用逻辑:在
shouldReuseRoute中优先判断目标路由是否已有存储实例,确保从任何允许的路由返回时都能复用RtgCreationComponent实例 - 简化冗余代码:移除无用的
routeLeftFrom变量,精简逻辑
内容的提问来源于stack exchange,提问作者SGR
相关产品推荐
相关产品推荐

