angular路由复用策略

379 阅读4分钟

一 路由复用策略

  • 与vue不同的是,他的复用策略提供了很多方法。让你根据不同机场景定义不同策略.
    • 添加onlyChildkeepAlive条件:当从详情页面返回主页时才复用主页页面
    // route.strategy.service.ts
    import {
      RouteReuseStrategy,
      DetachedRouteHandle,
      ActivatedRouteSnapshot,
    } from '@angular/router';
    @Injectable({ providedIn: 'root' })
    export class RouteStrategyService implements RouteReuseStrategy {
      private cacheRouters: Map<string, DetachedRouteHandle> = new Map<
          string,
          DetachedRouteHandle
      >();
    
      // 记录上一次路由,不过这个会可能会多次触发。所以不要在外部使用
      private previousUrl = '';
      /**
       * 判断当前路由是否需要缓存
       * 这个方法返回false时则路由发生变化并且其余方法会被调用
       * @param {ActivatedRouteSnapshot} future
       * @param {ActivatedRouteSnapshot} curr
       * @returns {boolean}
       * @memberof CacheRouteReuseStrategy
       */
      public shouldReuseRoute(
        future: ActivatedRouteSnapshot,
        curr: ActivatedRouteSnapshot,
      ): boolean {
        this.previousUrl = this.getFullRouteURL(curr) || '';
        return (
          future.routeConfig === curr.routeConfig &&
          JSON.stringify(future.params) === JSON.stringify(curr.params)
        );
      }
    
      /**
       * 当离开当前路由时这个方法会被调用
       * 如果返回 true 则 store 方法会被调用
       * @param {ActivatedRouteSnapshot} route
       * @returns {boolean}
       * @memberof CacheRouteReuseStrategy
       */
      public shouldDetach(route: ActivatedRouteSnapshot): boolean {
        return Boolean(route.data['keepAlive']);
      }
    
      /**
       * 将路由写入缓存
       * 在这里具体实现如何缓存 RouteHandle
       * 提供了我们离开的路由和 RouteHandle
       * @param {ActivatedRouteSnapshot} route
       * @param {DetachedRouteHandle} detachedTree
       * @memberof CacheRouteReuseStrategy
       */
      public store(
        route: ActivatedRouteSnapshot,
        detachedTree: DetachedRouteHandle,
      ): void {
        const url = this.getFullRouteURL(route);
        this.cacheRouters.set(url, detachedTree);
      }
    
      /**
       * 路由被导航 如果此方法返回 true 则触发 retrieve 方法
       * 如果返回 false 这个组件将会被重新创建
       * @param {ActivatedRouteSnapshot} route
       * @returns {boolean}
       * @memberof CacheRouteReuseStrategy
       */
      shouldAttach(route: ActivatedRouteSnapshot): boolean {
        const url = this.getFullRouteURL(route);
        if (Boolean(route.data['onlyChildkeepAlive'])) {
          // 根据不同的返回来源判断是否复用主页组件。
          // eg: 详情路径/list/detail;一般会包含主页路径/list
          return this.previousUrl.includes(url) && this.cacheRouters.has(url);
        } else {
          return this.cacheRouters.has(url);
        }
      }
    
      /**
       * 从缓存读取cached route
       * 提供当前路由的参数(刚打开的路由),并且返回一个缓存的 RouteHandle
       * 可以使用这个方法手动获取任何已被缓存的 RouteHandle
       * @param {ActivatedRouteSnapshot} route
       * @returns {(DetachedRouteHandle | null)}
       * @memberof CacheRouteReuseStrategy
       */
      public retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
        const url = this.getFullRouteURL(route);
        if (Boolean(route.data['keepAlive']) && this.cacheRouters.has(url)) {
          return this.cacheRouters.get(url);
        } else {
          return null;
        }
      }
    
      private getFullRouteURL(route: ActivatedRouteSnapshot): string {
        const { pathFromRoot } = route;
        let fullRouteUrlPath: string[] = [];
        pathFromRoot.forEach((item: ActivatedRouteSnapshot) => {
          fullRouteUrlPath = fullRouteUrlPath.concat(this.getRouteUrlPath(item));
        });
        return `/${fullRouteUrlPath.join('/')}`;
      }
    
      private getRouteUrlPath(route: ActivatedRouteSnapshot): string[] {
        return route.url.map((urlSegment) => urlSegment.path);
      }
    }
    
    
    • 添加一个函数,在组件被缓存的时候调用(场景:执行一些清除组件的service或定时器等任务)?
      • 使用canDeActive守卫,执行组件一个方法去清除组件的service或定时器等任务
      // candeactivate.guard.ts
      import { CanDeactivateFn } from '@angular/router';
      
      export const candeactivateGuard: CanDeactivateFn<unknown> = 
      (component, currentRoute, currentState, nextState) => {
        // 执行guardDestroy方法,组件内定义该方法销毁那些service或定时器等任务  
        (component as any)?.guardDestroy?.();
        return true;
      };
      
      // component.ts
      import { ProcessService } from '@app/service/process.service';
      import { Subscription } from 'rxjs';
      export class DomeComponent {
          subscriptionEvent() {
              this.subscription = this.processService.statusChanged.subscribe(
              async (result) => {
                  // 还可以使用异步方法
                  console.log(result)
              })
              console.log(this.processService.statusChanged)
          }
          unsubscriptionEvent() {
              this.subscription?.unsubscribe();
          }
          guardDestroy() {
              this?.unsubscriptionEvent()
          }
      }
      
      • 在缓存组件前,执行组件一个方法去清除组件的service或定时器等任务
      // route.strategy.service
      export class RouteStrategyService implements RouteReuseStrategy {
          /**
           * 将路由写入缓存
           * 在这里具体实现如何缓存 RouteHandle
           * 提供了我们离开的路由和 RouteHandle
           * @param {ActivatedRouteSnapshot} route
           * @param {DetachedRouteHandle} detachedTree
           * @memberof CacheRouteReuseStrategy
           */
          public store(
              route: ActivatedRouteSnapshot,
              detachedTree: DetachedRouteHandle,
          ): void {
              const url = this.getFullRouteURL(route);
              (detachedTree as any)?.componentRef?.instance?.cacheCompDestroy?.();
              this.cacheRouters.set(url, detachedTree);
          }
      }
      // component.ts
      import { ProcessService } from '@app/service/process.service';
      import { Subscription } from 'rxjs';
      export class DomeComponent {
          subscriptionEvent() {
              this.subscription = this.processService.statusChanged.subscribe(
              async (result) => {
                  // 还可以使用异步方法
                  console.log(result)
              })
              console.log(this.processService.statusChanged)
          }
          unsubscriptionEvent() {
              this.subscription?.unsubscribe();
          }
          cacheCompDestroy() {
              this.unsubscriptionEvent()
              console.log('cacheCompDestroy')
          }
      }
      
    • 添加一个函数,当组件被激活时调用(场景:恢复被清除组件的service或定时器等任务)?
      • 由于路由复用策略服务会被里面的接口函数会重复触发,所以不适合不适合在里面定义激活方法;那么CanActivate守卫就挺合适的。只要把路由复用服务定义注册在根注入器中就能在CanActivate守卫里面获取
      // canactivate.guard.ts
      import { inject } from '@angular/core';
      import { CanActivateFn, RouteReuseStrategy } from '@angular/router';
      export const canactivateGuard: CanActivateFn = (route, state) => {
        // 触发前置守卫
        const routeStrategyService = inject(RouteReuseStrategy);
        const url = state.url.split('?')[0];
        const exec = Boolean(route.data['keepAlive']) || 
        (
            Boolean(route.data['onlyChildkeepAlive']) 
            && 
            routeStrategyService.globalPreviousUrl.includes(url)
        )
        // 没有被缓存就不执行guardCanActivate方法
        if (exec) {
            const cacheRouter = (routeStrategyService as any)['cacheRouters'].get(url);
            const component = cacheRouter?.componentRef?.instance;
            component?.guardCanActivate()
        }
        return true;
      };
      // component.ts
      import { ProcessService } from '@app/service/process.service';
      import { Subscription } from 'rxjs';
      export class DomeComponent {
          subscriptionEvent() {
              this.subscription = this.processService.statusChanged.subscribe(
              async (result) => {
                  // 还可以使用异步方法
                  console.log(result)
              })
              console.log(this.processService.statusChanged)
          }
          unsubscriptionEvent() {
              this.subscription?.unsubscribe();
          }
          guardCanActivate() {
              this.subscriptionEvent()
          }
      }
      
    • 如何获取上一次路由?
      • ‘路由复用策略服务’暴露出的接口会触发多次,不适合记录上一次路由,最好就是在app.component.ts中记录
      // route.strategy.service.ts
      export class RouteStrategyService implements RouteReuseStrategy {
          // 记录上一次路由
          public globalPreviousUrl = '';
      }
      // app.component.ts
      import { RouteStrategyService } from '@app/service/route.strategy.service';
      export class AppComponent implements OnInit {
          ngOnInit(): void {
              this.router.events.subscribe(ev => {
                  if (ev instanceof NavigationEnd) {
                      this.routeStrategyService.globalPreviousUrl = ev.url;
                  }
              });
          }
      }
      

    注册自定路由策略

    import { NgModule } from '@angular/core';
    import { RouterModule, RouteReuseStrategy, Routes } from '@angular/router';
    import { HomeComponent } from './home.component';
    import { AboutComponent } from './about.component';
    import { RouteStrategyService } from './route.strategy.service';
    const routes: Routes = [ 
        { path: '', component: HomeComponent }, 
        { path: 'about', component: AboutComponent } 
    ]; 
    @NgModule({ 
        imports: [RouterModule.forRoot(routes)], 
        providers: [ { provide: RouteReuseStrategy, useClass: RouteStrategyService } ],
        exports: [RouterModule] 
    }) 
    export class AppRoutingModule { }