Skip to the solution
MediumFrontend9 min read

What is a route resolver and when is it useful?

1.2k views

The problem

Question presented to candidate: "A product page shows an empty template with spinners for half a second, then jumps when data arrives. A colleague suggests a route resolver. What is a resolver, when does it actually help, and what does it cost?"

What a strong answer should cover:

  • A resolver (ResolveFn<T>) is a function listed under a route's resolve key; the router runs it after all guards pass and before the component is created, and stores the result in the route's data.
  • The component receives the value through ActivatedRoute.data or, with withComponentInputBinding(), as an input named after the key.
  • Cost: the navigation (and the old screen) waits for the slowest resolver, so slow APIs make the app feel frozen; show progress via router events.
  • Behaviour details: Promise or Observable allowed; the router uses the first emitted value; an Observable that completes without emitting cancels the navigation; an error becomes a NavigationError; returning a RedirectCommand redirects.
  • Re-run rules: by default resolvers re-run when path or matrix parameters change, not on query changes (runGuardsAndResolvers).
  • When not to use one: data that can stream in, or that should not block the URL change.

Clarifying questions expected:

  • "How slow is the API, and is a blank page or a frozen old page worse for these users?"
  • "What should happen when the product does not exist: a 404 page or an inline message?"

Code / implementation expected: Yes β€” a functional resolver that fetches by route id, redirects when not found, and binds to a component input.

  • resolver
  • data

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

AngularFunctional resolver with RedirectCommand, input binding and router-event timeline
import { ApplicationConfig, Component, Injectable, inject, input } from "@angular/core";
import {
  NavigationEnd, NavigationError, NavigationStart, RedirectCommand, ResolveEnd, ResolveFn,
  ResolveStart, Router, RouterOutlet, provideRouter, withComponentInputBinding,
} from "@angular/router";

type Product = { id: string; name: string };

@Injectable({ providedIn: "root" })
export class ProductApi {
  calls = 0;
  // Pretend network call: 40 ms, and id 404 does not exist.
  get(id: string): Promise<Product | null> {
    this.calls++;
    return new Promise((r) => setTimeout(() => r(id === "404" ? null : { id, name: `Product ${id}` }), 40));
  }
}

export const productResolver: ResolveFn<Product> = async (route) => {
  const router = inject(Router); // inject before the first await
  const api = inject(ProductApi);
  const id = route.paramMap.get("id")!;
  console.log(`  resolver start id=${id}`);
  const product = await api.get(id);
  console.log(`  resolver done  id=${id}`);
  if (!product) return new RedirectCommand(router.parseUrl("/not-found"));
  return product;
};

@Component({
  selector: "app-product",
  template: `<p>{{ product().name }}</p>`,
})
export class ProductComponent {
  // The resolved value arrives as an input, by its key in "resolve".
  product = input.required<Product>();
  constructor() { console.log("  ProductComponent constructed"); }
}

@Component({ selector: "app-nf", template: `<p>Not found</p>` })
export class NotFoundComponent {}

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(
      [
        { path: "products/:id", component: ProductComponent, resolve: { product: productResolver } },
        { path: "not-found", component: NotFoundComponent },
      ],
      withComponentInputBinding(),
    ),
  ],
};

@Component({ selector: "app-root", imports: [RouterOutlet], template: `<router-outlet />` })
export class AppComponent {
  readonly router = inject(Router);
  readonly api = inject(ProductApi);
  constructor() {
    const t0 = performance.now();
    const at = () => `${Math.round((performance.now() - t0) / 10) * 10}`.padStart(4) + "ms";
    this.router.events.subscribe((e) => {
      if (e instanceof NavigationStart) console.log(`${at()} NavigationStart ${e.url}`);
      if (e instanceof ResolveStart) console.log(`${at()} ResolveStart`);
      if (e instanceof ResolveEnd) console.log(`${at()} ResolveEnd`);
      if (e instanceof NavigationEnd) console.log(`${at()} NavigationEnd ${e.urlAfterRedirects}`);
      if (e instanceof NavigationError) console.log(`${at()} NavigationError ${e.error}`);
    });
  }
}

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 15 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 nextMedium

Explain how to perform unit testing on components with Signals using TestBed.

Continue