title: Error: Cannot reattach ActivatedRouteSnapshot created from a different route
body: <p>I am trying to implement the <code>RouteReuseStrategy</code> class. It works fine when I navigate to top level paths.</p>
  
  <p>As soon as a path has child paths and I navigate to the child path, then navigate back to a top level path I receive the following error:</p>
  
  <blockquote>
    <p>Error: Uncaught (in promise): Error: Cannot reattach ActivatedRouteSnapshot created from a different route</p>
  </blockquote>
  
  <p>I have created a <a href="https://plnkr.co/edit/GuQuWnW2GsfnBOVyWQRh?p=preview" rel="noreferrer">plunker</a> to demonstrate the error. I see the plunker does not work in IE 11, please view it in the latest version of <strong>Chrome</strong></p>
  
  <p>Steps to reproduce the error:</p>
  
  <p><strong>Step1:</strong>
  <a href="https://i.stack.imgur.com/Q6kWd.png" rel="noreferrer"><img src="https://i.stack.imgur.com/Q6kWd.png" alt="enter image description here"></a></p>
  
  <p><strong>Step2</strong>
  <a href="https://i.stack.imgur.com/BX0rV.png" rel="noreferrer"><img src="https://i.stack.imgur.com/BX0rV.png" alt="enter image description here"></a></p>
  
  <p><strong>Step3</strong>
  <a href="https://i.stack.imgur.com/4m9Rj.png" rel="noreferrer"><img src="https://i.stack.imgur.com/4m9Rj.png" alt="enter image description here"></a></p>
  
  <p><strong>Step4</strong>
  <a href="https://i.stack.imgur.com/cKZax.png" rel="noreferrer"><img src="https://i.stack.imgur.com/cKZax.png" alt="enter image description here"></a></p>
  
  <p>You can view the error in the console:
  <a href="https://i.stack.imgur.com/syVma.png" rel="noreferrer"><img src="https://i.stack.imgur.com/syVma.png" alt="enter image description here"></a></p>
  
  <p>I have tried the implementation found on this <a href="https://www.softwarearchitekt.at/post/2016/12/02/sticky-routes-in-angular-2-3-with-routereusestrategy.aspx" rel="noreferrer">article</a></p>
  
  <pre><code>export class CustomReuseStrategy implements RouteReuseStrategy {
  
      handlers: {[key: string]: DetachedRouteHandle} = {};
  
      shouldDetach(route: ActivatedRouteSnapshot): boolean {
          console.debug('CustomReuseStrategy:shouldDetach', route);
          return true;
      }
  
      store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
          console.debug('CustomReuseStrategy:store', route, handle);
          this.handlers[route.routeConfig.path] = handle;
      }
  
      shouldAttach(route: ActivatedRouteSnapshot): boolean {
          console.debug('CustomReuseStrategy:shouldAttach', route);
          return !!route.routeConfig &amp;&amp; !!this.handlers[route.routeConfig.path];
      }
  
      retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
          console.debug('CustomReuseStrategy:retrieve', route);
          if (!route.routeConfig) return null;
          return this.handlers[route.routeConfig.path];
      }
  
      shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
          console.debug('CustomReuseStrategy:shouldReuseRoute', future, curr);
          return future.routeConfig === curr.routeConfig;
      }
  
  }
  </code></pre>
  
  <p>And the implementation of this stackoverflow <a href="https://stackoverflow.com/questions/41280471/how-to-implement-routereusestrategy-shoulddetach-for-specific-routes-in-angular#answer-41515648">answer</a></p>
  
  <pre><code>/**
   * 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 &amp;&amp; !!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 &amp;&amp; queryParamsMatch);
              return paramsMatch &amp;&amp; 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 (===)
       * @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;
      }
  }
  </code></pre>
  
  <p>Is <code>RouteReuseStrategy</code> ready for child <code>paths</code>? Or is there another way to get <code>RouteReuseStrategy</code> working with paths that contain child <code>paths</code></p>