自定义Angular路由复用策略(页面前进后退时,能保持之前的状态)

本文介绍了如何在Angular单页应用中通过自定义RouteReuseStrategy解决页面离开时需要保留用户输入的问题,包括shouldDetach、store等方法的实现,以及如何在路由配置中使用自定义策略和配置路由数据以控制页面复用行为。
摘要由CSDN通过智能技术生成

1、问题提出
在基于Angular的SPA应用中,应用通过路由在各个页面之间进行导航。 默认情况下,用户在离开一个页面时,这个页面(组件)会被Angular销毁,用户的输入信息也随之丢失,当用户再次进入这个页面时,看到的是一个新生成的页面(组件),之前的输入信息都没了。

配置的前端项目就是基于Angular的,工作中遇到了这样的问题,部分页面需要保存用户的输入信息,用户再次进入页面时需要回到上一次离开时的状态,部分页面每次都要刷新页面,不需要保存用户信息。而页面间的导航正是通过路由实现的,Angular的默认行为不能满足我们的需求!

2、解决思路
针对以上问题,通过查阅Angular的相关资料可以发现,Angular提供了RouteReuseStrategy接口,通过实现这个接口,可以让开发者自定义路由复用策略。
2.1 RouteReuseStrategy接口
我们先来看看RouteReuseStrategy的接口定义:

image.png
这个接口只定义了5个方法,每个方法的作用如下:

shouldDetach
路由离开时是否需要保存页面,这是实现自定义路由复用策略最重要的一个方法。
其中:

返回值为true时,路由离开时保存页面信息,当路由再次激活时,会直接显示保存的页面。

返回值为false时,路由离开时直接销毁组件,当路由再次激活时,直接初始化为新页面。

store
如果shouldDetach方法返回true,会调用这个方法来保存页面。

shouldAttach
路由进入页面时是否有页面可以重用。 true: 重用页面,false:生成新的页面

retrieve
路由激活时获取保存的页面,如果返回null,则生成新页面

shouldReuseRout
决定跳转后是否可以使用跳转前的路由页面,即跳转前后跳转后使用相同的页面

在这个默认的路由复用策略中,只有当跳转前和跳转后的路由一致时,才会复用页面。只要跳转前和跳转后的路由不一致,页面就会被销毁。

有鉴于此,我们需要实现一个自定义的路由复用策略,实现针对不同的路由,能够有不同的行为。同时,也要能兼容现有代码,不能对现有代码做大规模的修改。

3、代码如下
3.1 首先建一个 ZwRouteReuseStrategy 类实现 RouteReuseStrategy 接口,自定义其中方法。
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);
  }
}

}
3.2 配置路由重用策略为自定义策略
为了使用自定义的路由复用策略,需要在应用的根路由模块providers中使用自定义的路由复用策略。

image.png
3.3 配置路由
在路由配置中,按需配置路由的data属性。如需要保存页面,则设置reuse值为true,如不需要保存页面,不配置该属性。例如:
在这里插入图片描述

{
	path:'',
	components:component,
	data:{
		reuse:true,
		title:''
	}
}

image.png
此路由配置下,访问/list页面会在路由离开时会被保存,再次进入该页面都会恢复到上一次离开该页面时的状态。

转自:https://www.jianshu.com/p/da770baa252b

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值