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

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

作者: 来日可期_久 | 来源:发表于2020-07-07 15:52 被阅读0次

    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,如不需要保存页面,不配置该属性。例如:

    image.png

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

    相关文章

      网友评论

          本文标题:自定义Angular路由复用策略(页面前进后退时,能保持之前的状

          本文链接:https://www.haomeiwen.com/subject/tanaqktx.html