如何为 Angular 2 中的特定路由实现 RouteReuseStrategy shouldDetach [英] How to implement RouteReuseStrategy shouldDetach for specific routes in Angular 2

查看:38
本文介绍了如何为 Angular 2 中的特定路由实现 RouteReuseStrategy shouldDetach的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 Angular 2 模块,我在其中实现了路由,并希望在导航时存储状态.
用户应该能够:

I have an Angular 2 module in which I have implemented routing and would like the states stored when navigating.
The user should be able to:

  1. 使用搜索公式"搜索文档
  2. 导航到其中一个结果
  3. 导航回searchresult" - 不与服务器通信

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

This is possible including RouteReuseStrategy.
The question is:
How do I implement that the document should not be stored?

所以路由路径documents"的状态应该被存储,路由路径documents/:id"'状态不应该被存储?

So the route path "documents"'s state should be stored and the route path "documents/:id"' state should NOT be stored?

推荐答案

嘿安德斯,好问题!

我和您有几乎相同的用例,并且想做同样的事情!用户搜索 > 获取结果 > 用户导航到结果 > 用户返回 > BOOM 快速返回结果,但您不想存储用户的特定结果导航到.

I've got almost the same use case as you, and wanted to do the same thing! User search > get results > User navigates to result > User navigates back > BOOM blazing fast return to results, but you don't want to store the specific result that the user navigated to.

tl;dr

您需要有一个实现 RouteReuseStrategy 的类,并在 ngModule 中提供您的策略.如果要修改路由存储的时间,修改shouldDetach函数即可.当它返回 true 时,Angular 会存储路由.如果要在附加路由时进行修改,请修改shouldAttach 函数.当 shouldAttach 返回 true 时,Angular 将使用存储的路由代替请求的路由.这是一个 Plunker 供您玩耍.

You need to have a class that implements RouteReuseStrategy and provide your strategy in the ngModule. If you want to modify when the route is stored, modify the shouldDetach function. When it returns true, Angular stores the route. If you want to modify when the route is attached, modify the shouldAttach function. When shouldAttach returns true, Angular will use the stored route in place of the requested route. Here's a Plunker for you to play around with.

关于 RouteReuseStrategy

通过问这个问题,您已经了解 RouteReuseStrategy 允许您告诉 Angular 不是销毁组件,而是实际上将其保存以供以后重新渲染.这很酷,因为它允许:

By having asked this question, you already understand that RouteReuseStrategy allows you to tell Angular not to destroy a component, but in fact to save it for re-rendering at a later date. That's cool because it allows:

  • 减少服务器调用
  • 提高速度
  • AND 组件在默认情况下呈现出与原来相同的状态
  • Decreased server calls
  • Increased speed
  • AND the component renders, by default, in the same state it was left

如果您想暂时离开一个页面,即使用户已经输入了很多的文本,最后一个也很重要.由于过多的表单数量,企业应用程序会喜欢这个功能!

That last one is important if you would like to, say, leave a page temporarily even though the user has entered a lot of text into it. Enterprise applications will love this feature because of the excessive amount of forms!

这就是我想出的解决问题的方法.正如您所说,您需要使用@angular/router 在 3.4.1 及更高版本中提供的 RouteReuseStrategy.

This is what I came up with to solve the problem. As you said, you need to make use of the RouteReuseStrategy offered up by @angular/router in versions 3.4.1 and higher.

待办事项

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

First Make sure your project has @angular/router version 3.4.1 or higher.

下一步,创建一个文件来存放实现RouteReuseStrategy 的类.我调用了我的 reuse-strategy.ts 并将其放在 /app 文件夹中以妥善保管.现在,这个类应该是这样的:

Next, create a file which will house your class that implements RouteReuseStrategy. I called mine reuse-strategy.ts and placed it in the /app folder for safekeeping. For now, this class should look like:

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

export class CustomReuseStrategy implements RouteReuseStrategy {
}

(不用担心你的 TypeScript 错误,我们会解决所有问题)

(don't worry about your TypeScript errors, we're about to solve everything)

完成基础工作,将类提供给您的 app.module.请注意,您还没有编写 CustomReuseStrategy,但应该继续从 reuse-strategy.tsimport 它.同样 import { RouteReuseStrategy } from '@angular/router';

Finish the groundwork by providing the class to your app.module. Note that you have not yet written CustomReuseStrategy, but should go ahead and import it from reuse-strategy.ts all the same. Also import { RouteReuseStrategy } from '@angular/router';

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

最后一部分是编写控制路由是否分离、存储、检索和重新附加的类.在我们讨论旧的复制/粘贴之前,我将在这里对机制做一个简短的解释,据我所知.对于我所描述的方法,请参考下面的代码,当然,代码中有大量文档.

The final piece is writing the class which will control whether or not routes get detached, stored, retrieved, and reattached. Before we get to the old copy/paste, I'll do a short explanation of mechanics here, as I understand them. Reference the code below for the methods I'm describing, and of course, there's plenty of documentation in the code.

  1. 当您导航时,shouldReuseRoute 会触发.这个对我来说有点奇怪,但是如果它返回 true,那么它实际上会重用您当前所在的路线,并且不会触发任何其他方法.如果用户导航离开,我只会返回 false.
  2. 如果 shouldReuseRoute 返回 falseshouldDetach 会触发.shouldDetach 决定是否要存储路由,并返回一个 boolean 表示存储.这是您应该决定存储/不存储路径的地方,我将通过检查您想要存储的路径数组与route.routeConfig.path,如果 path 在数组中不存在,则返回 false.
  3. 如果 shouldDetach 返回 true,则触发 store,这是您存储任何您想要的关于路线的信息的机会.无论您做什么,您都需要存储 DetachedRouteHandle,因为 Angular 稍后会使用它来识别您存储的组件.下面,我将 DetachedRouteHandleActivatedRouteSnapshot 存储到我的类的本地变量中.
  1. When you navigate, shouldReuseRoute fires. This one is a little odd to me, but if it returns true, then it actually reuses the route you're currently on and none of the other methods are fired. I just return false if the user is navigating away.
  2. If shouldReuseRoute returns false, shouldDetach fires. shouldDetach determines whether or not you want to store the route, and returns a boolean indicating as much. This is where you should decide to store/not to store paths, which I would do by checking an array of paths you want stored against route.routeConfig.path, and returning false if the path does not exist in the array.
  3. If shouldDetach returns true, store is fired, which is an opportunity for you to store whatever information you would like about the route. Whatever you do, you'll need to store the DetachedRouteHandle because that's what Angular uses to identify your stored component later on. Below, I store both the DetachedRouteHandle and the ActivatedRouteSnapshot into a variable local to my class.

所以,我们已经看到了存储逻辑,但是如何导航一个组件呢?Angular 如何决定拦截你的导航并将存储的导航放在它的位置?

So, we've seen the logic for storage, but what about navigating to a component? How does Angular decide to intercept your navigation and put the stored one in its place?

  1. 同样,在 shouldReuseRoute 返回 false 后,shouldAttach 运行,这是您确定是要重新生成还是使用内存中的组件.如果您想重用已存储的组件,请返回 true,然后就可以了!
  2. 现在 Angular 会问您,您希望我们使用哪个组件?",您将通过从 retrieve 返回该组件的 DetachedRouteHandle 来指示.
  1. Again, after shouldReuseRoute has returned false, shouldAttach runs, which is your chance to figure out whether you want to regenerate or use the component in memory. If you want to reuse a stored component, return true and you're well on your way!
  2. Now Angular will ask you, "which component do you want us to use?", which you will indicate by returning that component's DetachedRouteHandle from retrieve.

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

That's pretty much all the logic you need! In the code for reuse-strategy.ts, below, I've also left you a nifty function that will compare two objects. I use it to compare the future route's route.params and route.queryParams with the stored one's. If those all match up, I want to use the stored component instead of generating a new one. But how you do it is up to you!

reuse-strategy.ts

/**
 * 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 方法来实现.它控制您保存哪些路线.

This implementation stores every unique route that the user visits on the router exactly once. This will continue to add to the components stored in memory throughout the user's session on the site. If you'd like to limit the routes that you store, the place to do it is the shouldDetach method. It controls which routes you save.

示例

假设您的用户从主页搜索某些内容,这会将他们导航到路径 search/:term,该路径可能显示为 www.yourwebsite.com/search/thingsearchedfor.搜索页面包含一堆搜索结果.你想存储这条路线,以防他们想回来!现在,他们点击搜索结果并导航到 view/:resultId,您不想想要存储它,因为他们可能只会出现一次.有了上面的实现,我只需更改 shouldDetach 方法!下面是它可能的样子:

Say your user searches for something from the homepage, which navigates them to the path search/:term, which might appear like www.yourwebsite.com/search/thingsearchedfor. The search page contains a bunch of search results. You'd like to store this route, in case they want to come back to it! Now they click a search result and get navigated to view/:resultId, which you do not want to store, seeing as they'll probably be there only once. With the above implementation in place, I would simply change the shouldDetach method! Here's what it might look like:

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

First off let's make an array of paths we want to store.

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

现在,在shouldDetach中,我们可以根据我们的数组检查route.routeConfig.path.

now, in shouldDetach we can check the route.routeConfig.path against our array.

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 和不是所有其他人!

Because Angular will only store one instance of a route, this storage will be lightweight, and we'll only be storing the component located at search/:term and not all the others!

其他链接

虽然目前还没有太多的文档,但这里有一些链接到现有内容:

Although there's not much documentation out there yet, here are a couple links to what does exist:

Angular 文档:https://angular.io/docs/ts/latest/api/router/index/RouteReuseStrategy-class.html

Angular Docs: https://angular.io/docs/ts/latest/api/router/index/RouteReuseStrategy-class.html

介绍文章:https://www.softwarearchitekt.at/post/2016/12/02/sticky-routes-in-angular-2-3-with-routereusestrategy.aspx

nativescript-angular 的 RouteReuseStrategy 的默认实现:https://github.com/NativeScript/nativescript-angular/blob/cb4fd3a/nativescript-angular/router/ns-route-reuse-strategy.ts

nativescript-angular's default Implementation of RouteReuseStrategy: https://github.com/NativeScript/nativescript-angular/blob/cb4fd3a/nativescript-angular/router/ns-route-reuse-strategy.ts

这篇关于如何为 Angular 2 中的特定路由实现 RouteReuseStrategy shouldDetach的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆