Angular RouteReuseStrategy 后退按钮/跨模块

2024-05-26

有关我的应用程序的信息(Angular 12):

  • 由 3 个模块组成,每个模块都有一个带有列表的概述页面和一些详细信息页面
  • 每条路线都有一个区域标签,因此我知道用户正在哪个模块中导航

所以我想实现 Angular 的 RouteReuseStrategy 来实现以下行为:

  • 每当用户从列表 -> 详细信息页面导航并使用后退按钮时,应重用列表组件(检测后退按钮触发器)

  • 每当用户从不同的模块/区域导航到列表组件时,不应重用列表组件

  • 每当用户从详细信息导航到另一个详细信息页面时,应该重用详细信息组件(默认行为?)

  • 每当用户通过导航到另一个模块或注销而离开模块时,应清除/销毁存储的组件

现在的情况:

  • 我实现了一个自定义 RouteReuseStrategy,它可以工作并且列表组件被重用 ✓

    • 只是滚动位置没有恢复,但这需要单独检查✕
  • 我想检查路线内的航空标签,但 ActivatedRouteSnapshot 是空的 ✕

  • 检测后退按钮按下,但事件经常触发,如果我实现基本后退标志,它就会中断✕

缺什么?

  • 检测后退按钮导航并修改组件的复用

  • 检测路由属于哪个模块,以修改重用或清理存储的组件

Code:

路线示例在模块 A 中

{
  path: 'lista',
  component: ListAComponent,
  data: {
    title: 'List overview',
    areaCategory: AreaCategory.A,
    reuseRoute: true,
  },
},
{
  path: 'lista/:id',
  component: DetailAComponent,
  data: {
    title: 'Detail',
    areaCategory: AreaCategory.A,
    reuseRoute: false,
  },
},

路线示例模块B

{
  path: 'listb',
  component: ListBComponent,
  data: {
    title: 'List overview',
    areaCategory: AreaCategory.B,
    reuseRoute: true,
  },
},
{
  path: 'listb/:id',
  component: DetailBComponent,
  data: {
    title: 'Detail',
    areaCategory: AreaCategory.B,
    reuseRoute: false,
  },
},

应用程序模块.ts

providers: [
{
  provide: RouteReuseStrategy,
  useClass: CustomReuseRouteStrategy,
}
],

在全球范围内应该没问题,还是我需要将其移动到 3 个模块中的每一个?

重用路由策略

@Injectable()
export class CustomReuseRouteStrategy implements RouteReuseStrategy {
  private handlers: { [key: string]: DetachedRouteHandle } = {};
  
  // Detect Backbutton-navigation
  back = false;
  constructor(location: LocationStrategy) {
    location.onPopState(() => {
      this.back = true;
    });
  }

  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    if (!route.routeConfig || route.routeConfig.loadChildren) {
      return false;
    }

    // Check route.data.reuse whether this route should be re used or not

    let shouldReuse = false;
    if (
      route.routeConfig.data &&
      route.routeConfig.data.reuseRoute &&
      typeof route.routeConfig.data.reuseRoute === 'boolean'
    ) {
      shouldReuse = route.routeConfig.data.reuseRoute;
    }
    return shouldReuse;
  }

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

  
  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    if (!this.back) {
      return false;
    }
    return !!this.handlers[this.getUrl(route)];
  }

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

    //this.back = false; -> does not work fires to often

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

  
  shouldReuseRoute(future: ActivatedRouteSnapshot, current: ActivatedRouteSnapshot): boolean {
    /** We only want to reuse the route if the data of the route config contains a reuse true boolean */

    let reUseUrl = false;
    if (future.routeConfig && future.routeConfig.data && typeof future.routeConfig.data.reuseRoute === 'boolean') {
      reUseUrl = future.routeConfig.data.reuseRoute;
    }

    //const defaultReuse = future.routeConfig === current.routeConfig; -> used for navigating to same component but routeConfigs are empty therefore always match?
    return reUseUrl;
  }

  private getUrl(route: ActivatedRouteSnapshot): string {
    if (route.routeConfig) {
      const url = route.routeConfig.path;
      return url;
    }
  }

  clearHandles() {
    for (const key in this.handlers) {
      if (this.handlers[key]) {
        this.destroyHandle(this.handlers[key]);
      }
    }
    this.handlers = {};
  }

  private destroyHandle(handle: DetachedRouteHandle): void {
    const componentRef: ComponentRef<any> = handle['componentRef'];
    if (componentRef) {
      componentRef.destroy();
    }
  }
}

我注意到_routerStateActivatedRouteSnapshot 内部保存着 url,可用于区分模块,但我宁愿从路由数据中检查areaCategory,但奇怪的是,shouldReuseRoute 方法中的未来和当前的ActivatedRouteSnapshots 大部分都是空的

另外,我不确定是否使用 _routerState 这样的内部值,因为我听说这些值不是固定的,可以随时更改

空的未来和当前快照的日志(只有网址有用)

为什么应用组件代替Detail or 列表组件? 也许这就是数据为空的原因?

我需要获取正确的路线/组件来访问区域类别以实现所需的行为。

按照这里的要求,这是一个简单的堆栈闪电战 https://stackblitz.com/edit/angular-ivy-onz5ma?file=src/app/module-c/routing-c.module.ts用我的设置

如果我错过了什么,请告诉我,非常感谢您的帮助


这尊重你的条件

  1. 每当用户从列表 -> 详细信息页面导航并使用后退按钮时,应该重用列表组件(检测后退按钮触发器)。是的。假设你没有list -> detail -> list导航
  2. 每当用户从不同的模块/区域导航到列表组件时,不应重用列表组件。是的。假设不同的区域不能有相同的areaCategory tag
  3. 每当用户从详细信息导航到另一个详细信息页面时,应该重用详细信息组件(默认行为?)。Yes
  4. 每当用户通过导航到另一个模块或注销而离开模块时,应清除/销毁存储的组件是的,与2相同。

我添加了一些额外的注释和图表以供您理解RouteReuseStrategy调用这将更清楚如何使用它。

下面的图表和伪代码显示了 Angular 核心如何使用该策略(这是基于我的观察,我没有找到官方文档):

transition(current, future) {
  if (shouldReuseRoute(future, current)) {
    // No navigation is performed, same route is recycled 
    return current;
  } else {
    if (shouldDetach(current)) {
      // If not reused and shouldDetach() == true then store
      store(current, getRouteHandler(current));
    }
    if (shouldAttach(future)) {
      // If not reused and shouldAttach() == true then retrieve
      return createRouteFromHandler(retrieve(future));
    } else {
      // If shouldAttach() == false do not recycle
      return future;
    }
  }
}

当然,这是一个例子。getRouteHandler and createRouteFromHandler仅作为示例介绍,不区分路由组件、路由实例和路由快照。

@Injectable()
export class CustomReuseRouteStrategy implements RouteReuseStrategy {
  private handlers: { [key: string]: DetachedRouteHandle } = {};

  clearHandlers() {
    Object.keys(this.handlers).forEach(h => {
      // https://github.com/angular/angular/issues/15873
      (h as any).componentRef.destroy();
    })
    this.handlers = {};
  }

  areSameArea(future: ActivatedRouteSnapshot, current: ActivatedRouteSnapshot): boolean {
    return future.routeConfig.data && current.routeConfig.data
      && future.routeConfig.data.areaCategory === current.routeConfig.data.areaCategory;
  }

  /**
   * This function decides weather the current route should be kept.
   * If this function returns `true` nor attach or detach procedures are called
   * (hence none of shouldDetach, shouldAttach, store, retrieve). If this function
   * returns `false` an attach/detach procedure is initiated.
   */
  shouldReuseRoute(future: ActivatedRouteSnapshot, current: ActivatedRouteSnapshot): boolean {
    console.log('shouldReuseRoute', future, current);

    if (!this.areSameArea(future, current)) {
      // Changed area, clear the cache
      this.clearHandlers();
    }

    return this.getUrl(future) === this.getUrl(current);
  }

  /**
   * DETACH PROCEDURE: if performing a detach, this function is called, if returns
   * `true` then store is called, to store the current context.
   */
  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    console.log('shouldDetach', route);
    // We always detach them (you never mentioned pages that are not recycled
    // by default)
    return true;
  }

  store(route: ActivatedRouteSnapshot, handler: DetachedRouteHandle): void {
    console.log('store', route, this.getUrl(route));
    if (!handler && this.getUrl(route)) return;
    this.handlers[this.getUrl(route)] = handler;
  }

  /**
   * ATTACH PROCEDURE: if performing an attach, this function is called, if returns
   * `true` then retrieve is called, to store the current context.
   */
  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    console.log('shouldAttach', route);
    return !!this.handlers[this.getUrl(route)];
  }

  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    console.log('retrieve', route, this.getUrl(route));
    return this.getUrl(route) && this.handlers[this.getUrl(route)];
  }


  private getUrl(route: ActivatedRouteSnapshot): string {
    // todo : not sure this behaves properly in case of parametric routes
    return route.routeConfig && route.routeConfig.path;
  }

}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Angular RouteReuseStrategy 后退按钮/跨模块 的相关文章

随机推荐