Route Parameters
Most real applications need to navigate to a screen that depends on a specific piece of data — a product page, a user profile, an order detail. Route parameters let you encode those dynamic values directly in the URL, so /products/42 and /products/99 resolve to the same component but render different data. Angular exposes those values through the ActivatedRoute service and, more conveniently in modern Angular, through component input binding. This page covers how to declare parameterized routes and the several ways to read their values reactively.
Declaring a parameterized route
A route parameter is a path segment prefixed with a colon. The router matches any value in that position and captures it under the name you choose.
import { Routes } from '@angular/router';
import { ProductDetailComponent } from './product-detail.component';
export const routes: Routes = [
{ path: 'products/:id', component: ProductDetailComponent },
// multiple params are fine too
{ path: 'users/:userId/orders/:orderId', component: OrderDetailComponent },
];
A navigation to /products/42 matches the first route and exposes id = '42'. Note that parameter values are always strings, even when they look numeric — convert them explicitly when you need a number.
Reading params with paramMap
The ActivatedRoute service gives you access to the current route’s data. Its paramMap is an Observable<ParamMap> that emits a fresh map every time the parameters change. Subscribing to the observable (rather than reading a snapshot) is important: when the user navigates from /products/42 to /products/99, Angular reuses the same component instance and only the observable fires again.
import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { toSignal } from '@angular/core/rxjs-interop';
import { map } from 'rxjs';
@Component({
selector: 'app-product-detail',
standalone: true,
template: `
@if (id(); as productId) {
<h1>Product #{{ productId }}</h1>
}
`,
})
export class ProductDetailComponent {
private route = inject(ActivatedRoute);
// paramMap is reactive; bridge it to a signal for the template
id = toSignal(this.route.paramMap.pipe(map((p) => p.get('id'))));
}
ParamMap offers a small, safe API for reading values.
| Method | Returns | Use when |
|---|---|---|
get(name) | string | null | Single value |
getAll(name) | string[] | A param that may repeat |
has(name) | boolean | Checking presence |
keys | string[] | Enumerating all params |
Avoid
route.snapshot.paramMapfor components that stay mounted across navigations. The snapshot is captured once and will not update when only the parameter changes, leaving you with stale data.
Component input binding (the modern way)
Since Angular 16 you can let the router bind route parameters directly to @Input() properties — or, in Angular 17.1+, to input() signals. Enable the feature once when providing the router.
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { AppComponent } from './app.component';
import { routes } from './app.routes';
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes, withComponentInputBinding())],
});
With binding enabled, a route parameter named id is matched to an input named id — no ActivatedRoute, no subscription, no manual cleanup.
import { Component, computed, inject, input } from '@angular/core';
import { ProductService } from './product.service';
@Component({
selector: 'app-product-detail',
standalone: true,
template: `
@let p = product();
@if (p) {
<h1>{{ p.name }}</h1>
<p>Price: {{ p.price | currency }}</p>
} @else {
<p>Loading product {{ id() }}…</p>
}
`,
})
export class ProductDetailComponent {
private products = inject(ProductService);
// bound from the :id path segment
id = input.required<string>();
// derive reactively whenever the param changes
productId = computed(() => Number(this.id()));
product = computed(() => this.products.byId(this.productId()));
}
withComponentInputBinding binds three sources to matching inputs: path parameters, query parameters, and static data/resolve values. If two sources share a name, the resolution order is data, then path params, then query params.
Input names must match parameter names exactly. A
:productIdsegment binds to an input calledproductId, notid. Rename one or the other to make them line up.
Choosing an approach
| Concern | ActivatedRoute + paramMap | withComponentInputBinding |
|---|---|---|
| Boilerplate | More (inject + subscribe) | Minimal |
| Works with signals | Via toSignal | Natively with input() |
Access to full ParamMap | Yes (getAll, keys) | No — one input per name |
| Available since | Always | Angular 16 / signals 17.1 |
For new code prefer input binding; reach for ActivatedRoute when you need the richer ParamMap API or have to combine several route data sources manually.
Best practices
- Always treat raw parameters as strings and convert (
Number(...),parseInt) deliberately, validating the result before use. - Read params reactively (
paramMapobservable or signal inputs) so components reused across navigations stay in sync. - Reserve
route.snapshotfor one-shot reads in components that are destroyed and recreated on every navigation. - Enable
withComponentInputBinding()once at bootstrap and let the router wire parameters intoinput()signals. - Keep input names identical to the parameter names in your route config to avoid silent
undefinedvalues. - Use
computed()to derive view models from parameter signals instead of imperatively reloading data in lifecycle hooks.