一尘不染

如何为Angular 2中的特定路线实施RouteReuseStrategy应该发布

javascript

我有一个Angular2模块,在其中实现了路由,并希望在导航时存储状态。用户应该能够:1.使用searchformula搜索文档2.导航到结果之一3.导航回到searchresult-无需与服务器通信

这可能包括RouteReuseStrategy。问题是:如何实现不应存储该文档?

因此,应该存储路由路径“ documents”的状态,而不应该存储路由路径“ documents /:id”的状态?


阅读 242

收藏
2020-04-25

共1个答案

一尘不染

嗨,安德斯,很好的问题!

我有几乎与您相同的用例,并且想做同样的事情!用户搜索>获取结果>用户导航到结果>用户导航> BOOM 迅速恢复结果
,但是您不希望存储用户导航到的特定结果。

tl; dr

您需要具有一个类,该类在中实现RouteReuseStrategy并提供您的策略ngModule。如果要在存储路径时进行修改,请修改shouldDetach功能。返回时true,Angular将存储路线。如果要在连接路由时进行修改,请修改shouldAttach功能。当shouldAttach返回true时,Angular将使用存储的路线代替请求的路线。这是一个Plunker供您玩耍。

关于RouteReuseStrategy

通过询问这个问题,您已经了解到RouteReuseStrategy允许您告诉Angular 不要
破坏组件,而实际上是保存它以便以后重新渲染。这很酷,因为它允许:

  • __服务器调用 减少
  • 提高 速度
  • 并且 ,默认情况下,组件以与原始状态相同的状态进行渲染

如果您想暂时离开某个页面,即使用户在其中输入了 很多 文本,那么最后一个页面也很重要。企业应用程序会喜欢这种功能,因为表单数量 过多

这就是我想出的解决问题的方法。如您所说,您需要利用RouteReuseStrategy3.4.1及更高版本中@ angular /
router提供的功能。

去做

首先, 请确保您的项目具有@ angular / router 3.4.1或更高版本。

接下来 ,创建一个文件来存放要实现的类RouteReuseStrategy。我打电话给我reuse- strategy.ts,并将其放在/app文件夹中以进行保管。现在,此类应如下所示:

import { RouteReuseStrategy } from '@angular/router';

export class CustomReuseStrategy implements RouteReuseStrategy {
}

(不必担心您的TypeScript错误,我们将解决所有问题)

通过为您的课程提供 基础知识
完成基础工作app.module。请注意,您尚未编写CustomReuseStrategy,但是应该import从头开始reuse-strategy.ts。也import { RouteReuseStrategy } from '@angular/router';

@NgModule({
    [...],
    providers: [
        {provide: RouteReuseStrategy, useClass: CustomReuseStrategy}
    ]
)}
export class AppModule {
}

最后一部分 是编写类,该类将控制是否分离,存储,检索和重新连接路由。在我理解旧的 复制/粘贴之前
,我将在这里对机制进行简短的解释。请参考以下代码,了解我正在描述的方法,当然, 代码中 有大量文档。

  1. 导航时shouldReuseRoute会触发。这对我来说有点奇怪,但是如果返回true,那么它实际上会重用您当前使用的路由,并且不会触发其他任何方法。如果用户正在导航,我只会返回false。
  2. 如果shouldReuseRoute返回false,则shouldDetach触发。shouldDetach确定您是否要存储路线,并返回一个boolean指示。 这是您应该决定存储/不存储路径的地方 ,我可以通过检查 存储的路径数组来完成route.routeConfig.path,如果path数组中不存在false,则返回false 。
  3. 如果shouldDetachreturn true,将store被触发,这是您存储有关路线的任何信息的机会。无论您做什么,都需要存储,DetachedRouteHandle因为Angular稍后会使用它来标识已存储的组件。在下面,我将DetachedRouteHandle和都存储ActivatedRouteSnapshot到类的局部变量中。

因此,我们已经了解了存储的逻辑,但是导航 组件又如何呢?Angular如何决定拦截您的导航并将已存储的导航放置在原处?

  1. 同样,shouldReuseRoute返回后falseshouldAttach运行,这是您确定是要重新生成还是使用内存中组件的机会。如果您想重用存储的组件,请返回true,一切顺利!
  2. 现在,Angular会问您“您要我们使用哪个组件?”,您将通过DetachedRouteHandle从返回该组件来进行指示retrieve

这几乎就是您需要的所有逻辑!在reuse-strategy.ts下面的代码中,我还为您提供了一个比较两个对象的漂亮函数。我用它来比较将来的路线route.paramsroute.queryParams已存储的路线。如果所有这些都匹配,我想使用存储的组件,而不是生成一个新组件。但是,如何操作
取决于您!

重用策略

/**
 * reuse-strategy.ts
 * by corbfon 1/6/17
 */

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

/** Interface for object which can store both: 
 * An ActivatedRouteSnapshot, which is useful for determining whether or not you should attach a route (see this.shouldAttach)
 * A DetachedRouteHandle, which is offered up by this.retrieve, in the case that you do want to attach the stored route
 */
interface RouteStorageObject {
    snapshot: ActivatedRouteSnapshot;
    handle: DetachedRouteHandle;
}

export class CustomReuseStrategy implements RouteReuseStrategy {

    /** 
     * Object which will store RouteStorageObjects indexed by keys
     * The keys will all be a path (as in route.routeConfig.path)
     * This allows us to see if we've got a route stored for the requested path
     */
    storedRoutes: { [key: string]: RouteStorageObject } = {};

    /** 
     * Decides when the route should be stored
     * If the route should be stored, I believe the boolean is indicating to a controller whether or not to fire this.store
     * _When_ it is called though does not particularly matter, just know that this determines whether or not we store the route
     * An idea of what to do here: check the route.routeConfig.path to see if it is a path you would like to store
     * @param route This is, at least as I understand it, the route that the user is currently on, and we would like to know if we want to store it
     * @returns boolean indicating that we want to (true) or do not want to (false) store that route
     */
    shouldDetach(route: ActivatedRouteSnapshot): boolean {
        let detach: boolean = true;
        console.log("detaching", route, "return: ", detach);
        return detach;
    }

    /**
     * Constructs object of type `RouteStorageObject` to store, and then stores it for later attachment
     * @param route This is stored for later comparison to requested routes, see `this.shouldAttach`
     * @param handle Later to be retrieved by this.retrieve, and offered up to whatever controller is using this class
     */
    store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
        let storedRoute: RouteStorageObject = {
            snapshot: route,
            handle: handle
        };

        console.log( "store:", storedRoute, "into: ", this.storedRoutes );
        // routes are stored by path - the key is the path name, and the handle is stored under it so that you can only ever have one object stored for a single path
        this.storedRoutes[route.routeConfig.path] = storedRoute;
    }

    /**
     * Determines whether or not there is a stored route and, if there is, whether or not it should be rendered in place of requested route
     * @param route The route the user requested
     * @returns boolean indicating whether or not to render the stored route
     */
    shouldAttach(route: ActivatedRouteSnapshot): boolean {

        // this will be true if the route has been stored before
        let canAttach: boolean = !!route.routeConfig && !!this.storedRoutes[route.routeConfig.path];

        // this decides whether the route already stored should be rendered in place of the requested route, and is the return value
        // at this point we already know that the paths match because the storedResults key is the route.routeConfig.path
        // so, if the route.params and route.queryParams also match, then we should reuse the component
        if (canAttach) {
            let willAttach: boolean = true;
            console.log("param comparison:");
            console.log(this.compareObjects(route.params, this.storedRoutes[route.routeConfig.path].snapshot.params));
            console.log("query param comparison");
            console.log(this.compareObjects(route.queryParams, this.storedRoutes[route.routeConfig.path].snapshot.queryParams));

            let paramsMatch: boolean = this.compareObjects(route.params, this.storedRoutes[route.routeConfig.path].snapshot.params);
            let queryParamsMatch: boolean = this.compareObjects(route.queryParams, this.storedRoutes[route.routeConfig.path].snapshot.queryParams);

            console.log("deciding to attach...", route, "does it match?", this.storedRoutes[route.routeConfig.path].snapshot, "return: ", paramsMatch && queryParamsMatch);
            return paramsMatch && queryParamsMatch;
        } else {
            return false;
        }
    }

    /** 
     * Finds the locally stored instance of the requested route, if it exists, and returns it
     * @param route New route the user has requested
     * @returns DetachedRouteHandle object which can be used to render the component
     */
    retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {

        // return null if the path does not have a routerConfig OR if there is no stored route for that routerConfig
        if (!route.routeConfig || !this.storedRoutes[route.routeConfig.path]) return null;
        console.log("retrieving", "return: ", this.storedRoutes[route.routeConfig.path]);

        /** returns handle when the route.routeConfig.path is already stored */
        return this.storedRoutes[route.routeConfig.path].handle;
    }

    /** 
     * Determines whether or not the current route should be reused
     * @param future The route the user is going to, as triggered by the router
     * @param curr The route the user is currently on
     * @returns boolean basically indicating true if the user intends to leave the current route
     */
    shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
        console.log("deciding to reuse", "future", future.routeConfig, "current", curr.routeConfig, "return: ", future.routeConfig === curr.routeConfig);
        return future.routeConfig === curr.routeConfig;
    }

    /** 
     * This nasty bugger finds out whether the objects are _traditionally_ equal to each other, like you might assume someone else would have put this function in vanilla JS already
     * One thing to note is that it uses coercive comparison (==) on properties which both objects have, not strict comparison (===)
     * Another important note is that the method only tells you if `compare` has all equal parameters to `base`, not the other way around
     * @param base The base object which you would like to compare another object to
     * @param compare The object to compare to base
     * @returns boolean indicating whether or not the objects have all the same properties and those properties are ==
     */
    private compareObjects(base: any, compare: any): boolean {

        // loop through all properties in base object
        for (let baseProperty in base) {

            // determine if comparrison object has that property, if not: return false
            if (compare.hasOwnProperty(baseProperty)) {
                switch(typeof base[baseProperty]) {
                    // if one is object and other is not: return false
                    // if they are both objects, recursively call this comparison function
                    case 'object':
                        if ( typeof compare[baseProperty] !== 'object' || !this.compareObjects(base[baseProperty], compare[baseProperty]) ) { return false; } break;
                    // if one is function and other is not: return false
                    // if both are functions, compare function.toString() results
                    case 'function':
                        if ( typeof compare[baseProperty] !== 'function' || base[baseProperty].toString() !== compare[baseProperty].toString() ) { return false; } break;
                    // otherwise, see if they are equal using coercive comparison
                    default:
                        if ( base[baseProperty] != compare[baseProperty] ) { return false; }
                }
            } else {
                return false;
            }
        }

        // returns true only after false HAS NOT BEEN returned through all loops
        return true;
    }
}

行为

此实现将用户访问的每个唯一路由准确地存储在路由器上一次。这将继续添加到站点上整个用户会话中存储在内存中的组件。如果您想限制您存储的路线,则可以使用该shouldDetach方法。它控制着您保存的路由。

假设您的用户从首页中搜索了一些内容,然后将其导航到该路径search/:term,该路径可能显示为www.yourwebsite.com/search/thingsearchedfor。搜索页面包含一堆搜索结果。您想存储这条路线,以防他们想回来!现在,他们点击一个搜索结果,并获得导航到view/:resultId,你
希望店,看到他们很可能会出现一次。完成上述实现后,我只需更改shouldDetach方法即可!可能是这样的:

首先, 让我们创建一个要存储的路径数组。

private acceptedRoutes: string[] = ["search/:term"];

现在,shouldDetach我们可以route.routeConfig.path对照数组检查。

shouldDetach(route: ActivatedRouteSnapshot): boolean {
    // check to see if the route's path is in our acceptedRoutes array
    if (this.acceptedRoutes.indexOf(route.routeConfig.path) > -1) {
        console.log("detaching", route);
        return true;
    } else {
        return false; // will be "view/:resultId" when user navigates to result
    }
}

由于Angular将仅存储路线的一个实例,因此该存储将是轻量级的,我们将仅存储位于search/:term而不是所有其他组件的组件!

2020-04-25