如何在Angular 2

时间:2016-12-22 10:03:17

标签: javascript angular typescript angular-ui-router

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

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

那么应该存储路径路径“文档”的状态,并且不应存储路径路径“documents /:id”'状态?

9 个答案:

答案 0 :(得分:150)

嘿安德斯,好问题!

我的用例几乎和你一样,并且想要做同样的事情!用户搜索>得到结果>用户导航到结果>用户导航回来> BOOM 快速返回结果,但您不想存储用户导航到的特定结果。

<强> TL;博士

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

关于RouteReuseStrategy

通过提出这个问题,你已经明白RouteReuseStrategy允许你告诉Angular 而不是来销毁一个组件,但实际上是为了保存它以便以后重新渲染。这很酷,因为它允许:

  • 减少服务器调用
  • 提高速度
  • AND 默认情况下,组件呈现的状态与其保持的状态相同

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

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

<强> TODO

首先确保您的项目具有@ 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返回falseshouldDetach会触发。 shouldDetach确定您是否要存储路线,并返回boolean表示同样多的路线。 这是您应该决定存储/不存储路径的地方,我会通过检查您想要存储的路径数组来对route.routeConfig.path进行存储,然后返回如果数组中不存在path,则为false。
  3. 如果shouldDetach返回true,则store会被触发,这是您存储有关路线的任何信息的机会。无论您做什么,您都需要存储DetachedRouteHandle,因为Angular稍后会使用它来识别您存储的组件。下面,我将DetachedRouteHandleActivatedRouteSnapshot存储到我班级的本地变量中。
  4. 所以,我们已经看到了存储的逻辑,但是如何将导航到组件呢? Angular如何决定拦截您的导航并将存储的导航放在其位置?

    1. 同样,在shouldReuseRoute返回false后,shouldAttach运行,这是您了解是否要在内存中重新生成或使用该组件的机会。如果您想重复使用已存储的组件,请返回true,然后就可以了![/ li>
    2. 现在Angular会问您,&#34;您希望我们使用哪个组件?&#34;,您将通过从DetachedRouteHandle返回该组件retrieve来表明
    3. 这几乎是你需要的所有逻辑!在下面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。如果这些都匹配,我想使用存储的组件而不是生成一个新组件。但是你如何做到这一点取决于你!

      <强>重用strategy.ts

      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 } } 我们可以针对我们的数组检查search/:term

      {{1}}

      因为Angular将仅存储路由的一个实例,所以此存储将是轻量级的,我们只会存储位于{{1}}的组件,而不是所有其他组件!

      其他链接

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

      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

答案 1 :(得分:25)

不要被接受的答案吓倒,这非常简单。这里是您需要的快速答案。我建议至少阅读接受的答案,因为它充满了非常详细的信息。

此解决方案不会像接受的答案那样进行任何参数比较,但它可以很好地存储一组路径。

app.module.ts导入:

import { RouteReuseStrategy } from '@angular/router';
import { CustomReuseStrategy, Routing } from './shared/routing';

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

共享/ routing.ts:

export class CustomReuseStrategy implements RouteReuseStrategy {
 routesToCache: string[] = ["dashboard"];
 storedRouteHandles = new Map<string, DetachedRouteHandle>();

 // Decides if the route should be stored
 shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return this.routesToCache.indexOf(route.routeConfig.path) > -1;
 }

 //Store the information for the route we're destructing
 store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    this.storedRouteHandles.set(route.routeConfig.path, handle);
 }

//Return true if we have a stored route object for the next route
 shouldAttach(route: ActivatedRouteSnapshot): boolean {
    return this.storedRouteHandles.has(route.routeConfig.path);
 }

 //If we returned true in shouldAttach(), now return the actual route data for restoration
 retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    return this.storedRouteHandles.get(route.routeConfig.path);
 }

 //Reuse the route if we're going to and from the same route
 shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return future.routeConfig === curr.routeConfig;
 }
}

答案 2 :(得分:9)

要使用延迟加载的模块使用Chris Fremgen的策略,请将CustomReuseStrategy类修改为以下内容:

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

export class CustomReuseStrategy implements RouteReuseStrategy {
  routesToCache: string[] = ["company"];
  storedRouteHandles = new Map<string, DetachedRouteHandle>();

  // Decides if the route should be stored
  shouldDetach(route: ActivatedRouteSnapshot): boolean {
     return this.routesToCache.indexOf(route.data["key"]) > -1;
  }

  //Store the information for the route we're destructing
  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
     this.storedRouteHandles.set(route.data["key"], handle);
  }

  //Return true if we have a stored route object for the next route
  shouldAttach(route: ActivatedRouteSnapshot): boolean {
     return this.storedRouteHandles.has(route.data["key"]);
  }

  //If we returned true in shouldAttach(), now return the actual route data for restoration
  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
     return this.storedRouteHandles.get(route.data["key"]);
  }

  //Reuse the route if we're going to and from the same route
  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
     return future.routeConfig === curr.routeConfig;
  }
}

最后,在您的功能模块中&#39;路由文件,定义你的密钥:

{ path: '', component: CompanyComponent, children: [
    {path: '', component: CompanyListComponent, data: {key: "company"}},
    {path: ':companyID', component: CompanyDetailComponent},
]}

更多信息here

答案 3 :(得分:5)

另一个更有效,完整和可重用的实现。这个支持@UğurDinç等惰性加载模块,并集成@Davor路由数据标志。最好的改进是基于页面绝对路径自动生成(几乎)唯一标识符。这样,您不必在每个页面上都自己定义它。

标记任何要缓存的页面设置reuseRoute: true。它将以shouldDetach方法使用。

{
  path: '',
  component: MyPageComponent,
  data: { reuseRoute: true },
}

这是最简单的策略实现,无需比较查询参数。

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

export class CustomReuseStrategy implements RouteReuseStrategy {

  storedHandles: { [key: string]: DetachedRouteHandle } = {};

  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return route.data.reuseRoute || false;
  }

  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    const id = this.createIdentifier(route);
    if (route.data.reuseRoute) {
      this.storedHandles[id] = handle;
    }
  }

  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    const id = this.createIdentifier(route);
    const handle = this.storedHandles[id];
    const canAttach = !!route.routeConfig && !!handle;
    return canAttach;
  }

  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    const id = this.createIdentifier(route);
    if (!route.routeConfig || !this.storedHandles[id]) return null;
    return this.storedHandles[id];
  }

  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return future.routeConfig === curr.routeConfig;
  }

  private createIdentifier(route: ActivatedRouteSnapshot) {
    // Build the complete path from the root to the input route
    const segments: UrlSegment[][] = route.pathFromRoot.map(r => r.url);
    const subpaths = ([] as UrlSegment[]).concat(...segments).map(segment => segment.path);
    // Result: ${route_depth}-${path}
    return segments.length + '-' + subpaths.join('/');
  }
}

这也比较了查询参数。 compareObjects与@Corbfon版本相比有一点改进:遍历基础对象和比较对象的属性。请记住,可以使用lodash isEqual方法之类的外部更可靠的实现。

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

interface RouteStorageObject {
  snapshot: ActivatedRouteSnapshot;
  handle: DetachedRouteHandle;
}

export class CustomReuseStrategy implements RouteReuseStrategy {

  storedRoutes: { [key: string]: RouteStorageObject } = {};

  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return route.data.reuseRoute || false;
  }

  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    const id = this.createIdentifier(route);
    if (route.data.reuseRoute && id.length > 0) {
      this.storedRoutes[id] = { handle, snapshot: route };
    }
  }

  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    const id = this.createIdentifier(route);
    const storedObject = this.storedRoutes[id];
    const canAttach = !!route.routeConfig && !!storedObject;
    if (!canAttach) return false;

    const paramsMatch = this.compareObjects(route.params, storedObject.snapshot.params);
    const queryParamsMatch = this.compareObjects(route.queryParams, storedObject.snapshot.queryParams);

    console.log('deciding to attach...', route, 'does it match?');
    console.log('param comparison:', paramsMatch);
    console.log('query param comparison', queryParamsMatch);
    console.log(storedObject.snapshot, 'return: ', paramsMatch && queryParamsMatch);

    return paramsMatch && queryParamsMatch;
  }

  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    const id = this.createIdentifier(route);
    if (!route.routeConfig || !this.storedRoutes[id]) return null;
    return this.storedRoutes[id].handle;
  }

  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return future.routeConfig === curr.routeConfig;
  }

  private createIdentifier(route: ActivatedRouteSnapshot) {
    // Build the complete path from the root to the input route
    const segments: UrlSegment[][] = route.pathFromRoot.map(r => r.url);
    const subpaths = ([] as UrlSegment[]).concat(...segments).map(segment => segment.path);
    // Result: ${route_depth}-${path}
    return segments.length + '-' + subpaths.join('/');
  }

  private compareObjects(base: any, compare: any): boolean {

    // loop through all properties
    for (const baseProperty in { ...base, ...compare }) {

      // 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:
            // tslint:disable-next-line triple-equals
            if (base[baseProperty] != compare[baseProperty]) {
              return false;
            }
        }
      } else {
        return false;
      }
    }

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

如果您有生成唯一密钥的最佳方法,请在我的答案中注明,我将更新代码。

感谢所有分享解决方案的人。

答案 4 :(得分:3)

以下是工作!参考:https://www.cnblogs.com/lovesangel/p/7853364.html

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

export class CustomReuseStrategy implements RouteReuseStrategy {

    public static handlers: { [key: string]: DetachedRouteHandle } = {}

    private static waitDelete: string

    public static deleteRouteSnapshot(name: string): void {
        if (CustomReuseStrategy.handlers[name]) {
            delete CustomReuseStrategy.handlers[name];
        } else {
            CustomReuseStrategy.waitDelete = name;
        }
    }
   
    public shouldDetach(route: ActivatedRouteSnapshot): boolean {
        return true;
    }

   
    public store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
        if (CustomReuseStrategy.waitDelete && CustomReuseStrategy.waitDelete == this.getRouteUrl(route)) {
            // 如果待删除是当前路由则不存储快照
            CustomReuseStrategy.waitDelete = null
            return;
        }
        CustomReuseStrategy.handlers[this.getRouteUrl(route)] = handle
    }

    
    public shouldAttach(route: ActivatedRouteSnapshot): boolean {
        return !!CustomReuseStrategy.handlers[this.getRouteUrl(route)]
    }

    /** 从缓存中获取快照,若无则返回nul */
    public retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
        if (!route.routeConfig) {
            return null
        }

        return CustomReuseStrategy.handlers[this.getRouteUrl(route)]
    }

   
    public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
        return future.routeConfig === curr.routeConfig &&
            JSON.stringify(future.params) === JSON.stringify(curr.params);
    }

    private getRouteUrl(route: ActivatedRouteSnapshot) {
        return route['_routerState'].url.replace(/\//g, '_')
    }
}

答案 5 :(得分:1)

在我们的案例中,所有提到的解决方案都不足够。我们有以下小型企业应用程序:

  1. 简介页面
  2. 登录页面
  3. 应用程序(登录后)

我们的要求:

  1. 延迟加载的模块
  2. 多级路线
  3. 将所有路由器/组件状态存储在应用程序部分的内存中
  4. 在特定路线上使用默认角度重用策略的选项
  5. 注销时销毁存储在内存中的所有组件

我们的路线的简化示例:

const routes: Routes = [{
    path: '',
    children: [
        {
            path: '',
            canActivate: [CanActivate],
            loadChildren: () => import('./modules/dashboard/dashboard.module').then(module => module.DashboardModule)
        },
        {
            path: 'companies',
            canActivate: [CanActivate],
            loadChildren: () => import('./modules/company/company.module').then(module => module.CompanyModule)
        }
    ]
},
{
    path: 'login',
    loadChildren: () => import('./modules/login/login.module').then(module => module.LoginModule),
    data: {
        defaultReuseStrategy: true, // Ignore our custom route strategy
        resetReuseStrategy: true // Logout redirect user to login and all data are destroyed
    }
}];

重用策略:

export class AppReuseStrategy implements RouteReuseStrategy {

private handles: Map<string, DetachedRouteHandle> = new Map();

// Asks if a snapshot from the current routing can be used for the future routing.
public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return future.routeConfig === curr.routeConfig;
}

// Asks if a snapshot for the current route already has been stored.
// Return true, if handles map contains the right snapshot and the router should re-attach this snapshot to the routing.
public shouldAttach(route: ActivatedRouteSnapshot): boolean {
    if (this.shouldResetReuseStrategy(route)) {
        this.deactivateAllHandles();
        return false;
    }

    if (this.shouldIgnoreReuseStrategy(route)) {
        return false;
    }

    return this.handles.has(this.getKey(route));
}

// Load the snapshot from storage. It's only called, if the shouldAttach-method returned true.
public retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
    return this.handles.get(this.getKey(route)) || null;
}

// Asks if the snapshot should be detached from the router.
// That means that the router will no longer handle this snapshot after it has been stored by calling the store-method.
public shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return !this.shouldIgnoreReuseStrategy(route);
}

// After the router has asked by using the shouldDetach-method and it returned true, the store-method is called (not immediately but some time later).
public store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle | null): void {
    if (!handle) {
        return;
    }

    this.handles.set(this.getKey(route), handle);
}

private shouldResetReuseStrategy(route: ActivatedRouteSnapshot): boolean {
    let snapshot: ActivatedRouteSnapshot = route;

    while (snapshot.children && snapshot.children.length) {
        snapshot = snapshot.children[0];
    }

    return snapshot.data && snapshot.data.resetReuseStrategy;
}

private shouldIgnoreReuseStrategy(route: ActivatedRouteSnapshot): boolean {
    return route.data && route.data.defaultReuseStrategy;
}

private deactivateAllHandles(): void {
    this.handles.forEach((handle: DetachedRouteHandle) => this.destroyComponent(handle));
    this.handles.clear();
}

private destroyComponent(handle: DetachedRouteHandle): void {
    const componentRef: ComponentRef<any> = handle['componentRef'];

    if (componentRef) {
        componentRef.destroy();
    }
}

private getKey(route: ActivatedRouteSnapshot): string {
    return route.pathFromRoot
        .map((snapshot: ActivatedRouteSnapshot) => snapshot.routeConfig ? snapshot.routeConfig.path : '')
        .filter((path: string) => path.length > 0)
        .join('');
}
}

答案 6 :(得分:1)

我在实现自定义路由重用策略时遇到了这些问题:

  1. 对路线附加/取消执行操作:管理订阅,清理等;
  2. 仅保留最后一个参数化路由的状态:内存优化;
  3. 重用组件,而不是状态:使用状态管理工具管理状态。
  4. “无法重新附加从其他路由创建的ActivatedRouteSnapshot”错误;

所以我写了一个图书馆来解决这些问题。该库提供用于附加/分离挂钩的服务和装饰器,并使用路线的组件存储分离的路线,而不是路线的路径。

示例:

/* Usage with decorators */
@onAttach()
public onAttach(): void {
  // your code...
}

@onDetach()
public onDetach(): void {
  // your code...
}

/* Usage with a service */
public ngOnInit(): void {
  this.cacheRouteReuse
    .onAttach(HomeComponent) // or any route's component
    .subscribe(component => {
      // your code...
    });

  this.cacheRouteReuse
    .onDetach(HomeComponent) // or any route's component
    .subscribe(component => {
      // your code...
    });
}

库:https://www.npmjs.com/package/ng-cache-route-reuse

答案 7 :(得分:0)

除了(Corbfon接受的答案)和克里斯·弗雷姆根(Chris Fremgen)简短而直接的解释之外,我还想添加一种更灵活的方法来处理应该使用重用策略的路由。

两个答案都将要缓存的路由存储在数组中,然后检查当前路由路径是否在数组中。此检查是通过shouldDetach方法完成的。

我发现这种方法不灵活,因为如果我们想更改路线名称,则需要记住还要在CustomReuseStrategy类中更改路线名称。我们可能会忘记更改它,或者我们团队中的其他开发人员可能甚至在不知道RouteReuseStrategy存在的情况下决定更改路线名称。

我们可以使用RouterModule对象在data中直接标记它们,而不是将要缓存的路由存储在数组中。这样,即使我们更改了路由名称,重用策略仍将适用。

{
  path: 'route-name-i-can-change',
  component: TestComponent,
  data: {
    reuseRoute: true
  }
}

然后在shouldDetach方法中利用它。

shouldDetach(route: ActivatedRouteSnapshot): boolean {
  return route.data.reuseRoute === true;
}

答案 8 :(得分:0)

以上所有答案都很棒,但如果您有延迟加载路由器并且嵌套过多,则它们都无法正常工作。

为了克服这个问题,需要更改用于比较路由的 shouldReuseRoute 和路径:

Path A: abc/xyx/3
Path B: abc/rty/8

<abc>
 <router-outlet></router-outlet>
</abc>

/* If we move from pathA to pathB or vice versa, 
 * then  `routeConfig` will be same since we are routing within the same abc, 
 * but to store the handle properly, you should store fullPath as key.
*/

  shouldReuseRoute(
    future: ActivatedRouteSnapshot,
    curr: ActivatedRouteSnapshot
  ): boolean {
  
    return future.routeConfig === curr.routeConfig;
  }


  private getPathFromRoot(route: ActivatedRouteSnapshot) {
    return (route["_urlSegment"]["segments"] as UrlSegment[])
      .map((seg) => seg.path)
      .join("/");
  }