0

I was working on a custom router reuse strategy that works locally with no issues, but when I deploy it to services like Vercel or Netlify, the app routing does not work. I click on router links to change the page, but the page component is not properly loaded, unless I reload.

Router Reuse Strategy is provided on app module

imports: [BrowserModule, AppRoutingModule],
providers: [
    { provide: RouteReuseStrategy, useClass: CustomRouteReuseStrategy },
  ],

App routing:

const routes: Routes = [
  {
    path: '',
    pathMatch: 'full',
    component: Page1Component,
  },
  {
    path: 'about',
    component: Page2Component,
  },
  {
    path: 'test',
    children: [
      { path: '', component: Page3Component, pathMatch: 'full' },
      { path: 'page4', component: Page4Component },
      { path: 'page4/:id', component: Page4Component },
      { path: ':id', component: Page3Component },
    ],
  },
  {
    path: '**',
    redirectTo: '',
  },
];

@NgModule({
  declarations: [],
  imports: [RouterModule.forRoot(routes, { onSameUrlNavigation: 'reload' })],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Links:

<a routerLink="/">home</a>
<a routerLink="/about">about</a>
<a routerLink="/test">test</a>
<a routerLink="/test/page4">page 4</a>

Custom Router Reuse

export class CustomRouteReuseStrategy implements RouteReuseStrategy {
  private storedRoutes: { [key: string]: DetachedRouteHandle }[] = [];

  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return true;
  }

  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle) {
    const path = route.routeConfig.component.name;
    const hasRoute = this.storedRoutes.some((i) => i[path]);
    if (handle && !hasRoute) {
      this.storedRoutes.push({ [path]: handle });
    }
  }

  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    let path, isPathStored, remove;

    if (route.children && !route.routeConfig?.component) {
      return false;
    }

    path = route.routeConfig.component.name;

    if (this.storedRoutes.length > 1) {
      isPathStored = Object.keys(this.storedRoutes[0])[0] === path;
    }

    if (isPathStored) {
      remove = this.storedRoutes.pop();
    } else if (this.storedRoutes.length > 1) {
      remove = this.storedRoutes.shift();
    }

    if (remove) {
      this.deactivateOutlet(remove[Object.keys(remove)[0]]);
    }

    const hasRoute = this.storedRoutes.some((i) => i[path]);
    return !!route.routeConfig && hasRoute;
  }

  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
    const path = route.routeConfig.component.name;
    const index = this.storedRoutes.findIndex((i) => i[path]);
    if (index > -1) {
      return this.storedRoutes[index][path];
    }

    return null;
  }

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

  private deactivateOutlet(handle) {
    const componentRef: ComponentRef<any> = handle.componentRef;
    if (componentRef) {
      componentRef.destroy();
    }
  }
}

Live url: https://angular-router-reuse.vercel.app/

mellunar
  • 104
  • 2
  • 8
  • Note that starting in Angular 11, `shouldReuseRoute` has swapped its arguments, it now has this signature: `shouldReuseRoute(curr: ActivatedRouteSnapshot, future: ActivatedRouteSnapshot): boolean` – zed Nov 28 '22 at 13:20

1 Answers1

0

As commented on this issue on GitHub, Angular changes the name of the components after building to production, so the component names are not unique and not safe to be used for comparison.

Another way to make safe to store a component reference in a unique way is to use the full route path, which is safe for children routes. To accomplish that we can create a listener to the router on app.component based on this answer, which allow us to have the value of the previous and current route, but it to access it we need a service.

Considering this, the custom router reuse class will look like this:

As we are going to use a constructor, the injectable decorator is going to be needed.

@Injectable({
  providedIn: 'root',
})
export class CustomRouteReuseStrategy implements RouteReuseStrategy {

Then we can import our service.

constructor(private sessionService: SessionService) {}

And use the full previousRoute as name for reference for store method.

store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle) {
    const path = this.sessionService.previousRoute;

And also the full currentRoute for shouldAttach and retrieve methods.

shouldAttach(route: ActivatedRouteSnapshot): boolean {
  const path = this.sessionService.currentRoute;

.

retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
    const path = this.sessionService.currentRoute;
mellunar
  • 104
  • 2
  • 8