Angular路由复用策略出现Cannot reattach ActivatedRouteSnapshot created from a different route错误

本文详细介绍了在Angular中实现路由复用的方法,包括定义复用逻辑、存储和检索路由缓存,以及如何通过shouldDetach、shouldAttach等方法判断路由是否可复用。同时,文章还分享了解决复用过程中出现的问题的经验。
摘要由CSDN通过智能技术生成

复用代码


自己写的angular路由复用,用着基本挺好。
定义的的顺序就是复用的逻辑,离开某路由页面时, shouldDetach觉得可复用后, store去保存路由,再次进入页面时, shouldAttach判断下可通过去缓存中拿到复用的路由后, retrieve返回路由缓存,最后 shouldReuseRoute对获得缓存路由与将进入的路由进行比对,一致通过则使用缓存的路由复现之前的画面。

// simple-reuse-strategy.ts
import {ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy} from '@angular/router'
import Debug from './debug.service'
import {ComponentRef} from '@angular/core'

export class SimpleReuseStrategy implements RouteReuseStrategy {
  public static handlers: { [key: string]: DetachedRouteHandle } = {}
  private static waitDelete: any
  private reuseModules: any = []   // 必须复用的模块,即进入此模块均可复用, 在index-routing 中的路由名
  private unReuseArr: any = ['home', 'credit_reports-paylog']     // 无需复用的模块
  private storeRoute: any

  /**
 * @desc 清除所有的路由缓存,还要把路由缓存中的组件销毁
   */
  static clearCacheRouters() {
    for (const key in this.handlers) {
      if (this.handlers[key]) {
        this.deactivateOutlet(this.handlers[key])
        delete this.handlers[key]
      }
    }
    Debug.log('this.handlers', {...this.handlers})
  }

  private static deactivateOutlet(handle: DetachedRouteHandle): void {
    const componentRef: ComponentRef<any> = handle ? handle['componentRef'] : null
    if (componentRef) {
      componentRef.destroy()
    }
  }

  /**
 * @desc 表示对所有路由允许复用 如果你有路由不想利用可以在这加一些业务逻辑判断
 * 顺位,1、明确不需要复用的,即unReuseArr中的,2、明确需要复用的,即reuseModules中的,3、后一个页面路由包含前一个路由
   */
  public shouldDetach(route: ActivatedRouteSnapshot): boolean {
    if (!route.routeConfig || route.routeConfig.loadChildren) {
      return false
    }
    let _find = false
    const len = this.unReuseArr.length
    for (let i = 0; i < len; i++) {
      if (this.getRouteUrl(route).indexOf(this.unReuseArr[i]) > -1) {
        _find = true
        break
      }
    }
    if (_find) return !_find          // 如果有明确不包含的,则返回false
    const pathname = window.location.pathname, routeUrl = route['_routerState'].url
    if (this.includeReuseModule(this.getPurePath(pathname))) {
      if (routeUrl.includes(pathname) && routeUrl.length > pathname.length) {
        // index/metadatas/add -> index/metadatas 不可
        return false
      }
      return true       // 只要;, ? 之前的path,包含可复用数组的路由,则可复用
    }
    return !_find
  }

  /**
 * @desc 当路由离开时会触发。按path作为key存储路由快照&组件当前实例对象
   */
  public store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    if (!route.routeConfig || route.routeConfig.loadChildren) return
    const key = this.getRouteUrl(route)
    this.storeRoute = key   // 存储上一个路由
    SimpleReuseStrategy.handlers[key] = handle
  }

  /**
 * @desc 当路由进入详情的时候才允许还原路由
   */
  public shouldAttach(route: ActivatedRouteSnapshot): boolean {
    if (!route.routeConfig || route.routeConfig.loadChildren) return false
    return !!SimpleReuseStrategy.handlers[this.getRouteUrl(route)]
  }

  /**
 * @desc 从缓存中获取快照,若无则返回null
   */
  public retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    if (!route.routeConfig || route.routeConfig.loadChildren) {
      return null
    }
    SimpleReuseStrategy.waitDelete = this.getRouteUrl(route)   // 存储当前路由
    return SimpleReuseStrategy.handlers[this.getRouteUrl(route)]
  }

  /**
 * @desc 进入路由触发,判断是否同一路由
   */
  public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    const include = (future.routeConfig && this.reuseModules.includes(future.routeConfig.path)) ||
      (curr.routeConfig && this.reuseModules.includes(curr.routeConfig.path))
    return include || (future.routeConfig === curr.routeConfig &&
      JSON.stringify(future.params) === JSON.stringify(curr.params))
  }

  public deleteRouteSnapshot(name: string): void {
    if (SimpleReuseStrategy.handlers[name]) {
      delete SimpleReuseStrategy.handlers[name]
    }
  }

  deleteAllRouteSnapshot() {
    SimpleReuseStrategy.handlers = {}
  }

  // 某一个链接是否包含可复用模块
  includeReuseModule(path) {
    let has = false
    const len = this.reuseModules.length
    for (let i = 0; i < len; i++) {
      if (path.indexOf(this.reuseModules[i]) > -1) {
        has = true
        break
      }
    }
    return has
  }

  // 如index/employees/detail;edit=1?name="好朋友", get index/employees/detail
  getPurePath(link) {
    const pathname = link.split('?')[0]
    return pathname ? pathname.split(';')[0] : ''
  }

  //eg: _credit_member-cards_MemberCardsComponent
  private getRouteUrl(route: ActivatedRouteSnapshot) {
    // return route['_routerState'].url.replace(/\//g, '_')
    return route['_routerState'].url.replace(/\//g, '_')
      + '_' + (route.routeConfig.loadChildren || route.routeConfig.component.toString().split('(')[0].split(' ')[1] )
  }
}

曾出现的问题:Cannot reattach ActivatedRouteSnapshot created from a different route
解决方法:

  • 若未混用懒加载策略 则完善shouldDetach方法即可
   /**
   * @desc 表示对所有路由允许复用 如果你有路由不想利用可以在这加一些业务逻辑判断
   */
  public shouldDetach(route: ActivatedRouteSnapshot): boolean {
    if (!route.routeConfig || route.routeConfig.loadChildren) {
      return false
    }
    return true
  }
  • 若混用了懒加载策略,在上面的getRouteUrl方法中,以前是直接使用route.routeConfig.path做key去缓存。即
  private getRouteUrl(route: ActivatedRouteSnapshot) {
    return route['_routerState'].url.replace(/\//g, '_')
  }

但path指的是配置在路由(XXXX.routing.ts)中的路由规则,不同模块有相同规则的机率相当大,比如每个模块可能都有 ’ ’
做默认路由,所以会出现错误,解决方法如下,使用path和componentRef的名称组合作为key缓存,基本不会重复。

//eg: _credit_member-cards_MemberCardsComponent  
   private getRouteUrl(route: ActivatedRouteSnapshot) {
       // return route['_routerState'].url.replace(/\//g, '_')
       return route['_routerState'].url.replace(/\//g, '_')
         + '_' + (route.routeConfig.loadChildren || route.routeConfig.component.toString().split('(')[0].split(' ')[1] ) 

参考资料


http://kavil.com.cn/2017/05/16/Angular-RouteReuse/(含component销毁)
https://segmentfault.com/a/1190000009971757(NavigationEnd监听)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值