Angular RouteReuseStrategy 后退按钮/交叉模块

标签 angular typescript angular-ui-router angular-router

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

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

  • 所以我想为以下行为实现 Angular 的 RouteReuseStrategy:
  • 每当用户从列表 -> 详细信息页面导航并使用后退按钮时,应重用列表组件(检测后退按钮触发器)
  • 每当用户从不同的模块/区域导航到列表组件时,不应重用列表组件
  • 每当用户从详细信息导航到另一个详细信息页面时,应该重用详细信息组件(默认行为?)
  • 每当用户通过导航到另一个模块或注销而离开模块时,应清除/销毁存储的组件

  • 现状:
  • 我实现了一个自定义的 RouteReuseStrategy,它可以工作并且列表组件被重用 ✓
  • 只有滚动位置没有恢复,这个需要单独检查✕

  • 我想检查路线内的区域标签,但 ActivatedRouteSnapshots 是空的 ✕
  • 检测后退按钮按下,但事件经常触发,如果我实现一个基本的后退标志,它会中断✕

  • 缺什么?
  • 检测返回按钮导航并修改组件的复用
  • 检测路由属于哪个模块以修改重用或清理存储的组件

  • 代码:
    示例路线 在模块 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,
      },
    },
    
    app.module.ts
    providers: [
    {
      provide: RouteReuseStrategy,
      useClass: CustomReuseRouteStrategy,
    }
    ],
    
    全局应该没问题,还是我需要将它移动到 3 个模块中的每一个?
    ReuseRouteStrategy
    @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();
        }
      }
    }
    
    我注意到 _routerState ActivatedRouteSnapshot 中包含 url,可用于区分模块,但我宁愿从路由数据中检查 areaCategory 但奇怪的是,shouldReuseRoute 方法中的 future 和当前 ActivatedRouteSnapshots 大多为空
    我也不确定是否使用像 _routerState 这样的内部值,因为我听说这些不是固定的,可以随时更改
    空 future 和当前快照的日志 (只有网址有用)
    Current & Future Route show AppComponent
    为什么是这样应用组件 而不是 详情 ListAComponent ?
    也许这就是数据为空的原因?
    我需要获得正确的路由/组件来访问 areaCategory 以实现所需的行为。
    根据这里的要求是一个简单的 stackblitz用我的设置
    如果我错过了什么,请告诉我,非常感谢您的帮助

    最佳答案

    这尊重你的条件

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

  • 我给你添加了一些额外的评论和图表来理解 RouteReuseStrategy调用这将更清楚如何使用它。
    遵循图表和伪代码,显示了 Angular 核心如何使用该策略(这是基于我的观察,我没有找到官方文档):
    enter image description here
    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;
        }
      }
    }
    
    当然,这是一个例子。 getRouteHandlercreateRouteFromHandler仅作为示例介绍,不区分路由组件、路由实例和路由快照。
    @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;
      }
    
    }
    
    

    关于Angular RouteReuseStrategy 后退按钮/交叉模块,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68902912/

    相关文章:

    angularjs - 使用ui-router解析时,如何访问解析后的数据?

    angularjs - 从历史记录中删除页面,因此 "back"将正常工作

    javascript - Angular JS ui 路由器不显示模板(没有 js 控制台错误)

    javascript - Angular http 服务循环

    javascript - 检查给定 ID 是否存在于 Javascript 中的嵌套对象中的有效方法

    angular - 选择多个项目的 PrimeNG 单选按钮

    angular - 更新到 Angular 9 和 raw-loader 不再工作

    typescript - 如何在Vue和TypeScript应用程序中全局正确注册axios

    javascript - 带参数的 Angular 路由器

    java - spring 工具套件中的 Angular 2