Angular使用路由复用策略实现页面前进后退时是否保持原状态

Angular使用路由复用策略实现页面前进后退时是否保持原状态

问题描述:

应用,有两个页包含多个查询条件及分页,选中一条数据进行编辑,更新至编辑页,编辑完成后返回列表页,此时,客户希望列表页保持离开时的状态不变,只单独更新这一条数据,其他情况下列表页正常初始化。

解决方案:

对列表页使用策略,根据上一个页面是否是编辑页来判断是否进行初始化。如果是编辑页跳转过来,此时列表页不应该执行ngOninit函数,且对单条数据进行更新,如果不是,那列表页执行ngOninit,正常初始化。

上代码:

1.新建route-strategy.service.ts

import {
  ActivatedRouteSnapshot,
  DetachedRouteHandle,
  RouteReuseStrategy
} from '@angular/router';
import { Injectable } from '@angular/core';

interface IRouteConfigData {
  reuse: boolean;
}

interface ICachedRoute {
  handle: DetachedRouteHandle;
  data: IRouteConfigData;
}
@Injectable()
export class ZwRouteReuseStrategy implements RouteReuseStrategy {
  private static routeCache = new Map<string, ICachedRoute>();
  private static waitDelete: string; // 当前页未进行存储时需要删除
  private static currentDelete: string; // 当前页存储过时需要删除

  /** 进入路由触发,判断是否是同一路由 */
  shouldReuseRoute(
    future: ActivatedRouteSnapshot,
    curr: ActivatedRouteSnapshot
  ): boolean {
    const IsReturn =
      future.routeConfig === curr.routeConfig &&
      JSON.stringify(future.params) == JSON.stringify(curr.params);
    return IsReturn;
  }

  /** 表示对所有路由允许复用 如果你有路由不想利用可以在这加一些业务逻辑判断,这里判断是否有data数据判断是否复用 */
  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    if (this.getRouteData(route)) {
      return true;
    }
    return false;
  }

  /** 当路由离开时会触发。按path作为key存储路由快照&组件当前实例对象 */
  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    // const url = this.getFullRouteUrl(route);
    const url = this.getRouteUrl(route);
    const data = this.getRouteData(route);

    if (
      ZwRouteReuseStrategy.waitDelete &&
      ZwRouteReuseStrategy.waitDelete === url
    ) {
      // 如果待删除是当前路由,且未存储过则不存储快照
      ZwRouteReuseStrategy.waitDelete = null;
      return null;
    } else {
      // 如果待删除是当前路由,且存储过则不存储快照
      if (
        ZwRouteReuseStrategy.currentDelete &&
        ZwRouteReuseStrategy.currentDelete === url
      ) {
        ZwRouteReuseStrategy.currentDelete = null;
        return null;
      } else {
        if (handle) {
          ZwRouteReuseStrategy.routeCache.set(url, { handle, data });
          this.addRedirectsRecursively(route);
        }
      }
    }
  }

  /** 若 path 在缓存中有的都认为允许还原路由 */
  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    // const url = this.getFullRouteUrl(route);
    const url = this.getRouteUrl(route);
    const handle = ZwRouteReuseStrategy.routeCache.has(url)
      ? ZwRouteReuseStrategy.routeCache.get(url).handle
      : null;
    const data = this.getRouteData(route);
    const IsReturn =
      data && ZwRouteReuseStrategy.routeCache.has(url) && handle != null;
    return IsReturn;
  }

  /** 从缓存中获取快照,若无则返回nul */
  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    const url = this.getRouteUrl(route);
    const data = this.getRouteData(route);
    const IsReturn =
      data && ZwRouteReuseStrategy.routeCache.has(url)
        ? ZwRouteReuseStrategy.routeCache.get(url).handle
        : null;

    return IsReturn;
  }

  private addRedirectsRecursively(route: ActivatedRouteSnapshot): void {
    const config = route.routeConfig;
    if (config) {
      if (!config.loadChildren) {
        const routeFirstChild = route.firstChild;
        const routeFirstChildUrl = routeFirstChild
          ? this.getRouteUrlPaths(routeFirstChild).join('/')
          : '';
        const childConfigs = config.children;
        if (childConfigs) {
          const childConfigWithRedirect = childConfigs.find(
            c => c.path === '' && !!c.redirectTo
          );
          if (childConfigWithRedirect) {
            childConfigWithRedirect.redirectTo = routeFirstChildUrl;
          }
        }
      }
      route.children.forEach(childRoute =>
        this.addRedirectsRecursively(childRoute)
      );
    }
  }
  private getRouteUrl(route: ActivatedRouteSnapshot) {
    return (
      route['_routerState'].url.replace(/\//g, '_') +
      '_' +
      (route.routeConfig.loadChildren ||
        route.routeConfig.component
          .toString()
          .split('(')[0]
          .split(' ')[1])
    );
  }

  private getFullRouteUrl(route: ActivatedRouteSnapshot): string {
    return this.getFullRouteUrlPaths(route)
      .filter(Boolean)
      .join('/')
      .replace('/', '_');
  }

  private getFullRouteUrlPaths(route: ActivatedRouteSnapshot): string[] {
    const paths = this.getRouteUrlPaths(route);
    return route.parent
      ? [...this.getFullRouteUrlPaths(route.parent), ...paths]
      : paths;
  }

  private getRouteUrlPaths(route: ActivatedRouteSnapshot): string[] {
    return route.url.map(urlSegment => urlSegment.path);
  }

  private getRouteData(route: ActivatedRouteSnapshot): IRouteConfigData {
    return (
      route.routeConfig &&
      (route.routeConfig.data as IRouteConfigData) &&
      route.routeConfig.data.reuse
    );
  }

  /** 用于删除路由快照*/
  public deleteRouteSnapshot(url: string): void {
    if (url[0] === '/') {
      url = url.substring(1);
    }
    url = url.replace('/', '_');
    if (ZwRouteReuseStrategy.routeCache.has(url)) {
      ZwRouteReuseStrategy.routeCache.delete(url);
      ZwRouteReuseStrategy.currentDelete = url;
    } else {
      ZwRouteReuseStrategy.waitDelete = url;
    }
  }
  public clear() {
    ZwRouteReuseStrategy.routeCache.clear();
  }
  public clearExcept(list) {
    if (!list || !ZwRouteReuseStrategy.routeCache) return;
    try {
      let waitDelete = [];
      ZwRouteReuseStrategy.routeCache.forEach((value: ICachedRoute, key) => {
        let handle: any = value.handle;
        let url = handle.route.value._routerState.snapshot.url;
        if (list.indexOf(url) < 0) {
          waitDelete.push(key);
        }
      });
      waitDelete.forEach(item => {
        ZwRouteReuseStrategy.routeCache.delete(item);
      });
    } catch (error) {
      console.log('clearExcept error', error);
    }
  }
}  

2.app.module.ts中引入

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

3.配置路由,各个模按需配置路由复用策略

在xxx.routing.module.ts文件中,在需要路由复用的模块路由上加上data: { reuse:true },这样该模块就不会默认执行ngOninit函数了。

const routes: Routes = [
  { path: '', redirectTo: 'list' },
  { path: "list", component: ListComponent, data: { reuse:true } },
  { path: "detail/:id", component: DetailComponent}
];

4.模块内部逻辑list.component.ts

constructor(private router:Router) {
    this.router.events.pipe(filter(event => event instanceof NavigationStart)).subscribe((event: NavigationStart) => {
      if(event.url==='/app/xxx/list'){ //  防止在其他模块时这里也执行,也就是在自己模块处理该问题即可
        //  当前是列表页,且来源页是编辑页时,不刷新页面
        if (this.router.url.indexOf("/app/xxx/detail")!==-1) {
              const idArr = this.router.url.split("/");
              const id = idArr[idArr.length-1];// 获取编辑的那条数据的id
              this.updateSingle(id);//单条更新
          } else if (this.router.url.indexOf("/app/xxx/detail")==-1) {
            // 不是编辑页跳转过来的,都走ngOninit函数
           this.ngOnInit();
          }
        }
      });
  }
 ngOnInit(): void{
   // 根据各个筛选条件及页码查询列表
   this.searchList();
 }
 searchList(){
   //获取列表数据
 }
 updateSingle(id){
   // 单条更新数据
 }

 

5.这样就大功告成了。

本文转载自: gaiery,版权归原作者所有,本博客仅以学习目的的传播渠道,不作版权和内容观点阐述,转载时根据场景需要有所改动。