skip to content

Reading Route State

Reading ActivatedRoute paramMap, queryParamMap and data as Observables, a snapshot, or inputs via withComponentInputBinding. Interviewers probe the stale snapshot when a component is reused.

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

explore

questions

5

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%

answer

  1. inject the route, not the router
  2. snapshot or Observable
  3. ParamMap get()
  4. text, or null

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.

solid answer

~40 s

The routed component injects `ActivatedRoute`, the router's object for the route that rendered it. `this.route.snapshot.paramMap.get('id')` reads the value at this moment; `this.route.paramMap` is an `Observable<ParamMap>` that emits the current map on subscribe and again whenever the params change. `ParamMap.get()` returns `string | null` (the first value if a key repeats), `getAll()` returns `string[]`, and `has()` tests presence. There is also `params`, the same data as a plain `Params` object, but `paramMap` is the recommended accessor. With `withComponentInputBinding()` enabled, an input named `id` receives the same value. Either way, `/users/42` yields the string `'42'`, so convert it with `Number(...)` before arithmetic or strict comparison.

code

ts · 12 lines
ts
import {Component, inject} from '@angular/core';
import {ActivatedRoute} from '@angular/router';

@Component({
  selector: 'app-user-profile',
  template: `<h1>User {{ userId }}</h1>`,
})
export class UserProfile {
  private readonly route = inject(ActivatedRoute);
  // One-off read: '42' for /users/42, null if the route had no :id
  readonly userId = Number(this.route.snapshot.paramMap.get('id'));
}

go deeper

for a junior

Know to inject ActivatedRoute, read snapshot.paramMap.get('id') or subscribe to paramMap, and that the value is a string or null.

for a middle

Explain ParamMap versus params, when the snapshot is enough, and where to convert the string so the rest of the component works with typed values.

for a senior

Choose the reactive form by default for detail pages that can link to themselves, and centralise parsing so invalid IDs are handled once.

for a principal

Set a team convention, either input binding with transforms or paramMap pipelines, so route reading is uniform and testable across features.

## Where route parameters live When Angular's router matches `/users/42` against a route `{path: 'users/:id', component: UserProfile}`, it builds a tree of **activated routes**. The node for this route records the matched URL segments, the **path parameters** (`id: '42'`), any **matrix parameters** on the segment, the shared **query parameters** and **fragment**, and the route's **data**. A routed component reaches its own node by injecting `ActivatedRoute`: ```ts private readonly route = inject(ActivatedRoute); ``` The router provides that `ActivatedRoute` through the injector the `router-outlet` creates for the component, so each routed component gets the node that rendered it. ## Three ways to read `id` | Access | Type | Updates? | |---|---|---| | `route.snapshot.paramMap.get('id')` | `string \| null` | no: the value at the moment you read it | | `route.paramMap` | `Observable<ParamMap>` | yes: emits on subscribe and on each change | | an input named `id` with `withComponentInputBinding()` | whatever the input declares | yes: set by the router | The snapshot is fine when you only need the value once. The Observable, or the input binding, is needed whenever the same component instance can be shown for another `id`, which is the case for next/previous links between users. ## `ParamMap` versus `params` `ActivatedRoute` exposes the parameters twice: - `params` / `snapshot.params` - a plain object, `{id: '42'}`, read as `params['id']`; - `paramMap` / `snapshot.paramMap` - a `ParamMap` with methods. `ParamMap` has: 1. `get(name)` - the value, or the first value when the key repeats, or `null`; 2. `getAll(name)` - every value as `string[]`, empty when absent; 3. `has(name)` - a boolean; 4. `keys` - the names present. The map form is the recommended one. Its methods make "missing" explicit (`null` rather than `undefined` from an index lookup), and `getAll()` handles repeated query keys such as `?tag=a&tag=b`. `queryParamMap` offers the same API for the query string. ## Always a string URL parameters are text. `get('id')` on `/users/42` returns `'42'`, never `42`. Code that does `id === 42`, or `id + 1` to build a Next link, silently misbehaves (`'421'`). Convert at the boundary: - `Number(map.get('id'))` for numeric IDs, then check `Number.isNaN`; - with input binding, an input `transform` such as `numberAttribute` from `@angular/core` does the conversion in one place. ## A minimal, reactive component ```ts @Component({ selector: 'app-user-profile', imports: [RouterLink], template: ` <h1>User {{ id() }}</h1> <a [routerLink]="['/users', id() + 1]">Next</a> `, }) export class UserProfile { private readonly route = inject(ActivatedRoute); readonly id = toSignal( this.route.paramMap.pipe(map((p) => Number(p.get('id')))), {requireSync: true}, ); } ``` `paramMap` emits its current value synchronously on subscribe, which is why `requireSync: true` is valid here. The Next link stays correct as the user moves between profiles. ## Common wrong turns - **Reading from `Router`.** `inject(Router).url` is the whole URL as a string; splitting it by `/` to find the id breaks as soon as the route gains a prefix, a matrix param or a query string. `ActivatedRoute` already holds the parsed value. - **Reading another route's params.** A component's `ActivatedRoute` covers its own route. Since Angular 22 it also includes values inherited from ancestors by default, but a parent cannot see a child's params through its own route; it would have to look at `route.firstChild`. - **Assuming a value is always present.** `get()` returns `null` for a missing key. With `users/:id` the id is always there, but optional matrix or query params are not, so handle `null` explicitly. - **Forgetting the query string lives elsewhere.** `?tab=posts` is on `queryParamMap`, not `paramMap`. ## What interviewers listen for - `ActivatedRoute`, not `Router`, is the source of this component's parameters. - The candidate knows `snapshot` does not update and `paramMap` does. - The candidate mentions the string type unprompted and converts it. - Bonus: knowing `withComponentInputBinding()` as the input-based alternative.

  • Why prefer paramMap over params?
    ParamMap makes absence explicit with get() returning null, handles repeated keys with getAll(), and has has() and keys. params is a plain object where a missing key is undefined and repeated values are harder to handle. Both carry the same data.
  • Where do query parameters come from on ActivatedRoute?
    From queryParamMap, or queryParams as a plain object, on the same ActivatedRoute, with snapshot versions too. Unlike path params, the query string is shared by every route in the tree, so any activated route sees the same values.

saying these in an interview costs you the question

  • Reading route parameters from the injected Router instead of ActivatedRoute
  • Expecting paramMap.get('id') to return a number for a numeric URL
  • Believing the snapshot updates when the same component shows another id
  • Thinking ParamMap.get returns undefined when a parameter is missing
  • Parsing the id out of router.url with string splitting
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, 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

In Angular 22, why can a child route's component read its parent's :userId from its own ActivatedRoute params, and what did 'emptyOnly' change?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

Since v22, paramsInheritanceStrategy defaults to 'always', so every route inherits its ancestors' params, static data and resolved data, and its own values win on a clash. The old default 'emptyOnly' inherited only for empty-path routes or componentless parents.

open as a page