Skip to the solution
HardFrontend9 min read

How do you implement custom routing strategies by extending RouteReuseStrategy?

329 views

The problem

Question presented to candidate: "On our product list, users type a search, scroll, open a product, and press Back. The list rebuilds from scratch and their search is gone. How would you keep that list alive across navigations with a custom RouteReuseStrategy, and what are the risks?"

What a strong answer should cover:

  • RouteReuseStrategy has five hooks: shouldDetach / store when leaving a route, shouldAttach / retrieve when entering one, and shouldReuseRoute for staying on the same config.
  • The default (BaseRouteReuseStrategy) reuses the live instance only when the route config is the same (for example /products/7 to /products/8) and never stores detached routes.
  • A custom strategy, usually extending BaseRouteReuseStrategy and provided with { provide: RouteReuseStrategy, useClass: ... }, stores a DetachedRouteHandle for chosen routes so the same component instance, DOM and state come back.
  • A detached component is not destroyed and gets no lifecycle hook on return; use the outlet's (attach) / (detach) events.
  • Risks: memory (stored trees live until evicted, destroyDetachedRouteHandle in 22.2), stale data, and cache keys that collide or never match.

Clarifying questions expected:

  • "Should the list also keep data fresh when the user returns, or show exactly what they left?"
  • "How many screens might be kept alive at once?"

Code / implementation expected: Yes β€” a keep-alive strategy for routes flagged in data, with constructor and destroy counts across navigations.

  • routing
  • performance

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

9 min read Β· 10 sections

Read the code

AngularKeep-alive RouteReuseStrategy for routes flagged with data.keepAlive
import { ApplicationConfig, Component, Injectable, OnDestroy, inject, signal } from "@angular/core";
import {
  ActivatedRouteSnapshot, BaseRouteReuseStrategy, DetachedRouteHandle, Router, RouteReuseStrategy,
  RouterOutlet, provideRouter,
} from "@angular/router";

let listCreated = 0;
let detailCreated = 0;

@Component({
  selector: "app-list",
  template: `<input [value]="search()" (input)="search.set($any($event.target).value)" /><p>list #{{ id }} search="{{ search() }}"</p>`,
})
export class ListComponent implements OnDestroy {
  id = ++listCreated;
  search = signal("");
  constructor() { console.log(`  ListComponent constructor (#${this.id})`); }
  ngOnDestroy() { console.log(`  ListComponent destroyed (#${this.id})`); }
}

@Component({ selector: "app-detail", template: `<p>detail #{{ id }}</p>` })
export class DetailComponent implements OnDestroy {
  id = ++detailCreated;
  constructor() { console.log(`  DetailComponent constructor (#${this.id})`); }
  ngOnDestroy() { console.log(`  DetailComponent destroyed (#${this.id})`); }
}

// Keep-alive for routes marked data: { keepAlive: true }. Everything else
// behaves exactly like the default (BaseRouteReuseStrategy).
@Injectable()
export class KeepAliveStrategy extends BaseRouteReuseStrategy {
  private stored = new Map<string, DetachedRouteHandle>();
  private key(route: ActivatedRouteSnapshot) {
    return route.pathFromRoot.map((r) => r.routeConfig?.path ?? "").join("/");
  }

  // Leaving a route: should its component tree be kept instead of destroyed?
  override shouldDetach(route: ActivatedRouteSnapshot) {
    return route.data["keepAlive"] === true;
  }
  override store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle | null) {
    console.log(`  store("${this.key(route)}", ${handle ? "handle" : "null"})`);
    if (handle) this.stored.set(this.key(route), handle);
    else this.stored.delete(this.key(route));
  }
  // Entering a route: do we have a kept tree to put back?
  override shouldAttach(route: ActivatedRouteSnapshot) {
    return this.stored.has(this.key(route));
  }
  override retrieve(route: ActivatedRouteSnapshot) {
    return this.stored.get(this.key(route)) ?? null;
  }
  // shouldReuseRoute is inherited: same route config means reuse the live instance.
}

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter([
      { path: "products", component: ListComponent, data: { keepAlive: true } },
      { path: "products/:id", component: DetailComponent },
    ]),
    { provide: RouteReuseStrategy, useClass: KeepAliveStrategy },
  ],
};

@Component({
  selector: "app-root",
  imports: [RouterOutlet],
  template: `<router-outlet (attach)="log('attach')" (detach)="log('detach')" />`,
})
export class AppComponent {
  readonly router = inject(Router);
  log(e: string) { console.log(`  outlet (${e}) event`); }
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 112 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextHard

What are preloading strategies?

Continue