skip to content

SPA Router

Angular's router: Routes arrays, links and navigation, guards and resolvers, lazy route chunks and navigation events. Interviewers probe it since routes decide bundle size and where auth is enforced.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

In Angular, how would you define routes for a blog with /posts, /posts/:slug and a 404 page, and does their order matter?

level: juniorimportance: must knowfreq 70%

answer

  1. a Routes array passed to provideRouter
  2. colon marks a parameter
  3. first match wins
  4. specific before general
  5. double asterisk goes last

basics

~10 s

Register a Routes array with provideRouter: 'posts', 'posts/:slug' and a final '**' route for the 404 page. Order matters: the router uses the first route that matches, so the wildcard goes last.

solid answer

~50 s

I export `routes: Routes` from `app.routes.ts` and register it with `provideRouter(routes)` in `ApplicationConfig`. The entries are `{ path: 'posts', component: PostList }`, `{ path: 'posts/:slug', component: PostDetail }` — the colon makes `slug` a route parameter matching exactly one URL segment — an empty-path redirect `{ path: '', redirectTo: 'posts', pathMatch: 'full' }`, and last `{ path: '**', component: NotFound }`. Paths never start with a slash. Order matters because the router walks the array top to bottom and uses the first route that matches the whole URL: a `**` placed first would catch everything, and a parameter route listed before a static one with the same shape, like `posts/:slug` before `posts/new`, would swallow it. A leaf route must consume the entire remaining URL, so `posts` does not match `/posts/hello`; the router moves on to `posts/:slug`.

code

ts · 18 lines
ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter, Routes } from '@angular/router';
import { PostList } from './posts/post-list';
import { NewPost } from './posts/new-post';
import { PostDetail } from './posts/post-detail';
import { NotFound } from './not-found';

export const routes: Routes = [
  { path: '', redirectTo: 'posts', pathMatch: 'full' },
  { path: 'posts', component: PostList, title: 'All posts' },
  { path: 'posts/new', component: NewPost, title: 'Write a post' }, // static before :slug
  { path: 'posts/:slug', component: PostDetail },
  { path: '**', component: NotFound, title: 'Page not found' }, // always last
];

export const appConfig: ApplicationConfig = {
  providers: [provideRouter(routes)],
};

go deeper

for a junior

Recall the Routes array, the colon parameter syntax, provideRouter, and that the ** route goes last.

for a middle

Explain first match wins, why leaf routes must consume the whole URL, and why static paths go before parameter paths of the same shape.

for a senior

Review a large route table for shadowed routes, choose between rendering and redirecting the 404, and keep ordering rules explicit in code review.

for a principal

Treat URLs as a public contract: plan redirects for renamed paths and decide how not-found pages behave for users and crawlers.

## The pieces of a route table In Angular's router, a **route** is a plain object of type `Route`; the table is an array typed `Routes` from `@angular/router`. The most common properties are: - **`path`** — the URL pattern, written **without a leading slash**. Segments are separated by `/`; a segment starting with `:` is a **route parameter** that matches exactly one URL segment. - **`component`** — the standalone component rendered in the `<router-outlet>` when the route is active. - **`redirectTo`** — send the navigation somewhere else instead of rendering. - **`pathMatch`** — `'prefix'` (default) or `'full'`, which matters for redirects and routes with children. - **`title`** and **`data`** — the page title and arbitrary static metadata. The table is registered with `provideRouter(routes)` in `ApplicationConfig`; the CLI generates `app.routes.ts` for it. ## The blog table ```ts export const routes: Routes = [ { path: '', redirectTo: 'posts', pathMatch: 'full' }, { path: 'posts', component: PostList, title: 'All posts' }, { path: 'posts/new', component: NewPost, title: 'Write a post' }, { path: 'posts/:slug', component: PostDetail }, { path: '**', component: NotFound, title: 'Page not found' }, ]; ``` | URL | Route that renders | Why | |---|---|---| | `/` | redirect, then `posts` | the empty path with `pathMatch: 'full'` matches only the empty URL | | `/posts` | `PostList` | static path, whole URL consumed | | `/posts/new` | `NewPost` | listed before the parameter route, so it wins | | `/posts/hello-angular` | `PostDetail`, `slug = 'hello-angular'` | one segment after `posts` | | `/posts/2026/hello` | `NotFound` | `:slug` matches one segment only; nothing else fits | | `/about` | `NotFound` | nothing earlier matches | ## How the router picks a route The router walks the array **in order** and takes the **first** route that can match the URL: 1. It compares the route's `path` with the start of the remaining URL segments. 2. A route without children must consume **all** remaining segments; if something is left over, that route fails and the router tries the next one. 3. A route with children consumes its part and hands the rest to its children; if they fail, the router backs off and continues with the next sibling. 4. The first route that succeeds wins; later routes are never considered, even if they would also match. That is why `posts` does not steal `/posts/hello-angular` even though its default `pathMatch` is `'prefix'`: it has no children to consume `hello-angular`, so it fails and the router moves on. It is also why **static before dynamic** matters when both shapes fit the same URL: with `posts/:slug` above `posts/new`, the URL `/posts/new` would render `PostDetail` with `slug = 'new'`. ## The wildcard `path: '**'` matches any URL, including one with many segments. Because the first match wins, it belongs **at the end** of its routes array; anywhere else it hides every route after it. There are two ways to use it: - `{ path: '**', component: NotFound }` renders the 404 page **at the URL the user typed**, which is usually what users and crawlers expect. - `{ path: '**', redirectTo: '/not-found' }` sends them to a dedicated URL, which loses the original address from the address bar. Since v21.1 a `**` can also sit inside a longer path with fixed segments after it; a path containing more than one `**` never matches. ## Parameter names and pitfalls - Parameter names start with a letter and may contain letters, digits, `_` and `-`. - A parameter never spans segments: `posts/:slug` does not match `/posts/a/b`. - A leading slash in `path`, a duplicated comma that leaves an `undefined` entry, or a route with neither `component`, `loadComponent`, `children`, `loadChildren` nor `redirectTo` is rejected by the router's configuration check in development builds. ## What interviewers listen for A correct table, the colon syntax, `pathMatch: 'full'` on the empty redirect, the wildcard last, and the explanation of **first match wins** with the "leaf routes must consume the whole URL" rule that makes the order of `posts` and `posts/:slug` harmless while `posts/new` versus `posts/:slug` is not.

  • Should the 404 route render a component or redirect to /not-found?
    Rendering `NotFound` on the `**` route keeps the URL the user typed, so they can see and fix it, and it is usually what you want. Redirecting to `/not-found` replaces the address, which loses that information. Either way the route must be the last entry in its array.
  • Does { path: 'posts' } without pathMatch: 'full' capture /posts/hello?
    No. A route without children has to consume the entire remaining URL. `posts` consumes one segment, `hello` is left over and there are no children to take it, so the route fails and the router tries the next one, `posts/:slug`.

The router reads the route table like a receptionist reading a list of rules from the top: the first rule that fits the visitor decides the room, so the catch-all rule "everyone else goes to reception" has to be the last line.

saying these in an interview costs you the question

  • Paths should start with a slash like '/posts'
  • The router picks the most specific route regardless of order
  • The wildcard route can go anywhere in the array
  • posts/:slug also matches /posts/2026/hello
  • A leaf route with default pathMatch captures any longer URL
open as a page

In an Angular app, how do you drive a top-of-page loading bar from the router, and why does a NavigationStart/NavigationEnd pair leave it stuck?

level: juniorimportance: must knowfreq 58%

basics

~10 s

Show the bar while Router.currentNavigation() is non-null, or start it on NavigationStart and stop it on NavigationEnd, NavigationCancel or NavigationError. Stopping only on NavigationEnd leaves it running after cancelled or failed navigations.

open as a page

In Angular, how do you write a functional canActivate guard that lets only admins into an /admin route and redirects everyone else?

level: juniorimportance: must knowfreq 80%

basics

~10 s

Write a CanActivateFn that injects the auth service, returns true for admins and returns a UrlTree (router.createUrlTree(['/login'])) or a RedirectCommand for everyone else, then list it in the route's canActivate array.

open as a page

In Angular, how do you lazy-load a route with loadComponent versus loadChildren, and what does a default export let you omit?

level: juniorimportance: must knowfreq 70%

basics

~10 s

loadComponent takes a function returning import() of one standalone component; loadChildren returns a module whose export is a Routes array. When the file uses export default, the .then(m => m.X) step can be dropped.

open as a page

In Angular, how do you configure child routes so a mail shell renders inbox, sent and message views in its own nested router-outlet?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Give the parent route a component and a children array, and put a <router-outlet> in that component's template. Child paths are relative to the parent, and the child component is rendered as a sibling right after the nested outlet element.

open as a page

In Angular, how do you pre-load an invoice with a ResolveFn before its edit page renders, and where does the component read it?

level: juniorimportance: must knowfreq 62%

basics

~10 s

Write a ResolveFn that injects the invoice service and returns the invoice, register it under the route's resolve key, and the router waits for it, then exposes the value on ActivatedRoute.data under that key.

open as a page

In an Angular component routed at users/:id, how do you read the id route parameter, and what type does it come back as?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Inject ActivatedRoute and call paramMap.get('id'), either once on route.snapshot or reactively on the route.paramMap Observable. The value is a string, or null when the parameter is absent; convert it yourself before using it as a number.

open as a page

In Angular's router, what is the difference between pathMatch 'full' and 'prefix', and why does an empty-path redirect need 'full'?

level: middleimportance: must knowfreq 65%

basics

~20 s

'prefix', the default, matches when the path is the start of the remaining URL; 'full' requires the path to be the whole remaining URL. An empty path is a prefix of every URL, so an empty-path redirect without 'full' would redirect everything.

open as a page

In Angular's router, how does a canMatch guard returning false differ from a canActivate guard returning false?

level: middleimportance: must knowfreq 58%

basics

~20 s

canMatch runs while the router matches the URL: false makes it skip that route config and try the next one, possibly a ** fallback. canActivate runs after a route has matched: false cancels the whole navigation.

open as a page

In Angular, how do you build a CanDeactivateFn that warns before leaving a form with unsaved changes, and what does it not catch?

level: middleimportance: must knowfreq 68%

basics

~20 s

A CanDeactivateFn<T> receives the component being left, checks its dirty state and returns true or a confirmation result (boolean, Observable or Promise). It sees only router navigations; reloads, tab closes and external URLs need beforeunload.

open as a page

In Angular's Router, what is the difference between navigate() and navigateByUrl(), and which one honours relativeTo and queryParams?

level: middleimportance: must knowfreq 62%

basics

~10 s

Router.navigate() builds a UrlTree from a commands array, honouring relativeTo, queryParams, queryParamsHandling and fragment, then calls navigateByUrl(). navigateByUrl() takes a complete URL string or UrlTree and applies no delta. Both return a Promise<boolean>.

open as a page

In Angular, when is reading ActivatedRoute.snapshot safe, and when must a component subscribe to paramMap instead?

level: middleimportance: must knowfreq 70%

basics

~20 s

The router reuses a component when the next URL matches the same route config, so a snapshot value copied into a field goes stale. Use the snapshot for one-off reads; use paramMap whenever the same component can show new params.

open as a page

In Angular's router, what does provideRouter(routes, withHashLocation()) change about the app's URLs, and when would you still choose it?

level: juniorimportance: should knowfreq 52%

basics

~10 s

withHashLocation() replaces the default PathLocationStrategy with HashLocationStrategy, so routes live after a # (example.com/#/orders/42). The server only ever receives the root path, so deep-link reloads work on hosts that cannot rewrite URLs.

open as a page

In Angular's router, what do the scrollPositionRestoration and anchorScrolling options of withInMemoryScrolling() do, and what happens without them?

level: middleimportance: should knowfreq 45%

basics

~20 s

Without withInMemoryScrolling() the router never scrolls, so a new route opens at the old offset. scrollPositionRestoration 'top' resets to the top, even on Back; 'enabled' restores the saved position on Back/Forward. anchorScrolling 'enabled' scrolls to the URL fragment's element.

open as a page

In Angular, when would you write a route's redirectTo as a function instead of a string, and what can that function use?

level: middleimportance: should knowfreq 38%

basics

~20 s

Use a string when the target is fixed apart from copied :params; use a function when the target needs logic. The function receives a partial route snapshot (params, queryParams, data, url), can call inject(), and returns a string or UrlTree, synchronously or asynchronously.

open as a page

In Angular route definitions, what do the title and data properties do, and which title wins when routes are nested?

level: middleimportance: should knowfreq 42%

basics

~20 s

title sets the document title when the route activates, as a string or a ResolveFn; with nesting, the deepest title on the primary outlet path wins. data attaches static metadata, such as an analytics id.

open as a page

In Angular, how do NavigationCancel, NavigationError and NavigationSkipped differ, and how do you tell why a navigation was cancelled?

level: middleimportance: should knowfreq 40%

basics

~10 s

NavigationCancel means the router deliberately stopped the navigation, NavigationError means something threw, and NavigationSkipped means it was ignored. The cancel event's code, a NavigationCancellationCode such as GuardRejected or Redirect, says why.

open as a page

In Angular, in what order does Router.events emit during a successful navigation, and which events can end a navigation?

level: middleimportance: should knowfreq 52%

basics

~10 s

A successful Angular navigation emits NavigationStart, RoutesRecognized, GuardsCheckStart, GuardsCheckEnd, ResolveStart, ResolveEnd, then NavigationEnd. Every navigation ends with exactly one of NavigationEnd, NavigationCancel, NavigationError or NavigationSkipped.

open as a page

In Angular, why are class-based route guards deprecated, and how do you migrate one to a functional guard that still uses injected services?

level: middleimportance: should knowfreq 50%

basics

~10 s

Since v15.2, listing an @Injectable class or InjectionToken in a route's guard arrays is deprecated. Rewrite it as a CanActivateFn that calls inject() synchronously, or wrap the class with mapToCanActivate([MyGuard]) while migrating.

open as a page

In Angular, what changes when you add withPreloading(PreloadAllModules) to provideRouter, and when does the router start preloading?

level: middleimportance: should knowfreq 58%

basics

~10 s

By default the router uses NoPreloading, so lazy chunks download only on navigation. withPreloading(PreloadAllModules) makes the router, after each completed navigation, download every lazy loadChildren and loadComponent chunk in the background.

open as a page

In Angular, what does a route's providers array create, which code can inject from it, and how long do those instances live?

level: middleimportance: should knowfreq 44%

basics

~20 s

A route's providers array makes the router create an EnvironmentInjector for that route and its children; their components, guards, resolvers and loaders can inject from it. It lives on after you leave unless withAutoCleanupInjectors is enabled.

open as a page

In Angular, how do you move a product list to page 3 without dropping its active filter query parameters, and what does queryParamsHandling control?

level: middleimportance: should knowfreq 55%

basics

~10 s

Navigate with empty commands, queryParams {page: 3} and queryParamsHandling 'merge' so current filters are kept. The option decides how new query params combine with current ones: 'replace' (default), 'merge' or 'preserve'.

open as a page

In Angular, why does routerLinkActive keep highlighting a Home link to '/' on every page, and how do routerLinkActiveOptions fix it?

level: middleimportance: should knowfreq 48%

basics

~20 s

RouterLinkActive matches by subset by default, so a link to '/' is contained in every URL and stays active. Setting routerLinkActiveOptions to {exact: true}, or to IsActiveMatchOptions with paths 'exact', restricts it to the exact URL.

open as a page

In Angular's router, how does a componentless parent route differ from an empty-path parent route with a component, and when do you use each?

level: middleimportance: should knowfreq 45%

basics

~20 s

A componentless parent has children but no component, so its children render in the outlet above it; it groups guards, resolvers, providers or lazy children. An empty-path parent with a component adds a layout shell without a URL segment.

open as a page

In Angular, how do you open a compose panel in a named router-outlet beside the inbox, and what does the resulting URL look like?

level: middleimportance: should knowfreq 35%

basics

~10 s

Add <router-outlet name="side" />, give a route outlet: 'side', and navigate with an outlets command such as [{outlets: {side: ['compose']}}]. The URL becomes /inbox(side:compose); setting side to null closes the panel.

open as a page

When an Angular resolver's request fails or the invoice does not exist, what happens to the navigation, and how should the resolver redirect instead?

level: middleimportance: should knowfreq 48%

basics

~20 s

An error thrown by a resolver fails the whole navigation: the router rolls back, emits NavigationError and leaves the user on the previous page. To send them elsewhere, the resolver returns (or, since 22.2, throws) a RedirectCommand.

open as a page

In Angular's router, when do resolvers run relative to guards, in what order across parent and child routes, and which emitted value is kept?

level: middleimportance: should knowfreq 45%

basics

~20 s

Resolvers run only after every guard in the navigation has passed; routes are resolved parent before child, one route at a time, the resolvers on a single route run concurrently, and the router keeps only each resolver's first emitted value.

open as a page

In Angular, how does withComponentInputBinding() bind route state to a routed component's inputs, and why can an input's default value disappear?

level: middleimportance: should knowfreq 52%

basics

~20 s

withComponentInputBinding() makes the router set the routed component's inputs from query params, path and matrix params, static and resolved data, matched by input name. By default an unmatched input is set to undefined, overwriting its declared default.

open as a page

An Angular search-results page loses its results, filters and scroll when users open a detail route and press Back. How do you keep it alive with RouteReuseStrategy?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Provide a custom RouteReuseStrategy whose shouldDetach returns true for the results route, store the DetachedRouteHandle, then let shouldAttach return true and retrieve return the handle on the way back. The component instance and DOM survive, so results, filters and scroll return intact.

open as a page

showing 1–30 of 46