AngularJSRoute Parameters

Route Parameters in Angular

Route parameters let you embed dynamic values directly in the URL — /users/42, /products/laptop-pro, /blog/2024/angular-routing. The component reads those values from the URL to load the right data.

Angular supports three types of URL-based data passing:

  • Route parameters — part of the path: /users/:id
  • Query parameters — after the ?: /search?q=angular&page=2
  • Fragment — after the #: /docs/routing#advanced
Defining Route Parameters

TS
// app.routes.ts
import { Routes } from '@angular/router';

export const routes: Routes = [
  { path: 'users',     component: UserListComponent },
  { path: 'users/:id', component: UserDetailComponent },  // :id is a parameter

  // Multiple parameters
  { path: 'blog/:year/:month/:slug', component: BlogPostComponent },

  // Optional segment (use separate routes or query params for optional data)
  { path: 'products/:category',     component: ProductListComponent },
  { path: 'products/:category/:id', component: ProductDetailComponent },
];
Reading Parameters: snapshot vs Observable

There are two ways to read route parameters:

  1. snapshot — reads the current value once; fine when the component is always destroyed and recreated on navigation
  2. paramMap observable — emits whenever the parameter changes; necessary when the same component instance is reused (e.g. navigating from /users/1 to /users/2)

TS
import { Component, inject, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { UserService } from './user.service';
import { User } from './user.model';

@Component({
  standalone: true,
  template: `
    <div *ngIf="user">
      <h1>{{ user.name }}</h1>
      <p>{{ user.email }}</p>
    </div>
  `,
})
export class UserDetailComponent implements OnInit {
  private route = inject(ActivatedRoute);
  private userService = inject(UserService);
  user: User | null = null;

  ngOnInit() {
    // Option 1: snapshot — reads once
    const id = this.route.snapshot.paramMap.get('id');
    if (id) this.loadUser(+id);

    // Option 2: observable — reacts to changes
    this.route.paramMap.subscribe(params => {
      const id = params.get('id');
      if (id) this.loadUser(+id);
    });
  }

  private loadUser(id: number) {
    this.userService.getUser(id).subscribe(user => (this.user = user));
  }
}
Note
The + prefix converts the string parameter to a number: +id is equivalent to Number(id). Route parameters are always strings in Angular.
Input Binding for Route Parameters (Angular 16+)

Angular 16 introduced a cleaner way to receive route parameters: bind them directly to component @Input properties using withComponentInputBinding().

TS
// main.ts — enable input binding
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter, withComponentInputBinding } from '@angular/router';

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes, withComponentInputBinding()),
  ],
});

TS
// user-detail.component.ts — receive :id as an @Input
import { Component, Input, OnChanges, inject } from '@angular/core';
import { UserService } from './user.service';

@Component({
  standalone: true,
  template: `<h1>User {{ id }}</h1>`,
})
export class UserDetailComponent implements OnChanges {
  @Input() id!: string;   // automatically populated from :id param

  private userService = inject(UserService);

  ngOnChanges() {
    // Called every time id changes (same component, different param)
    this.userService.getUser(+this.id).subscribe(/* ... */);
  }
}
Tip
With withComponentInputBinding(), route params, query params, and resolved route data are all automatically bound to matching @Input properties. This is the cleanest approach for Angular 16+ projects.
Query Parameters

Query parameters are ideal for optional, filter-like data that doesn't change which component is shown — search terms, pagination, sort order.

TS
// Navigate with query params
import { Router } from '@angular/router';

const router = inject(Router);

router.navigate(['/products'], {
  queryParams: { category: 'electronics', page: 2, sort: 'price-asc' },
});

// Preserve existing query params when navigating
router.navigate(['/products'], {
  queryParams: { page: 3 },
  queryParamsHandling: 'merge',    // keeps other existing params
  // queryParamsHandling: 'preserve' — keeps ALL existing params unchanged
});

HTML
<!-- routerLink with query params -->
<a
  [routerLink]="['/products']"
  [queryParams]="{ category: 'books', page: 1 }"
>Books</a>

<!-- Link that adds to existing query params -->
<a
  [routerLink]="['/products']"
  [queryParams]="{ page: nextPage }"
  queryParamsHandling="merge"
>Next Page</a>

TS
// Reading query params
import { ActivatedRoute } from '@angular/router';

const route = inject(ActivatedRoute);

// Snapshot (once)
const category = route.snapshot.queryParamMap.get('category');
const page     = +(route.snapshot.queryParamMap.get('page') ?? '1');

// Observable (reacts to changes)
route.queryParamMap.subscribe(params => {
  const category = params.get('category') ?? '';
  const page     = +(params.get('page') ?? '1');
  // load data...
});

// As a plain object
route.queryParams.subscribe(params => {
  console.log(params['category'], params['page']);
});
Fragment (#anchor)

TS
// Navigate with fragment
router.navigate(['/docs/routing'], { fragment: 'advanced' });
// URL becomes: /docs/routing#advanced

HTML
<a [routerLink]="['/docs/routing']" fragment="advanced">Advanced Routing</a>

TS
// Read the fragment
route.fragment.subscribe(fragment => {
  if (fragment) {
    document.querySelector(`#${fragment}`)?.scrollIntoView();
  }
});
Passing Static Data via Route Config

Use data in the route config to pass static metadata — page titles, breadcrumbs, permissions — to a component without URL-visible values.

TS
// app.routes.ts
const routes: Routes = [
  {
    path: 'admin/users',
    component: AdminUsersComponent,
    data: {
      title: 'User Management',
      breadcrumb: ['Admin', 'Users'],
      requiredRole: 'ADMIN',
    },
  },
];

// Reading in the component
const title = route.snapshot.data['title'];
const role  = route.snapshot.data['requiredRole'];
Comparison: Ways to Pass Data via URL

Method

Example URL

Best for

Route param

/users/42

Required, identity-defining values (ID, slug)

Query param

/search?q=angular&page=2

Optional filters, pagination, sorting

Fragment

/docs#section

In-page anchors, scroll targets

Route data

(not in URL)

Static metadata like title or required role

State (history)

(not in URL)

Transient data like flash messages

Navigation State (Transient Data)

Passing sensitive or large data through the URL is undesirable. Use router state to pass data invisibly through navigation history.

TS
// Sender component
router.navigate(['/checkout/success'], {
  state: { orderId: 'ORD-123', total: 99.99 },
});

// Receiver component
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';

@Component({ standalone: true, template: `...` })
export class CheckoutSuccessComponent {
  private router = inject(Router);

  // State is available via the router's current navigation
  orderId: string = this.router.getCurrentNavigation()?.extras.state?.['orderId'] ?? '';
  // or via window.history.state (after the navigation completes)
}
Warning
Router state is stored in window.history.state and is lost on page refresh. Do not rely on it for data that must survive a full reload — store that data in a service or session storage instead.
The ActivatedRoute Snapshot vs Observable Summary

Approach

When to use

snapshot.paramMap.get()

Component is always destroyed on navigation (most cases)

paramMap observable

Same component instance reused when only the param changes

@Input() binding (v16+)

Cleanest modern approach — Angular wires everything automatically

Summary: Use :paramName in route paths for required URL segments and read them via ActivatedRoute. Use query parameters for optional filter-like data. Angular 16+ withComponentInputBinding() is the cleanest approach — just declare an @Input with the same name as the parameter and Angular populates it automatically.