Angular
·6 min read·↗

How to Use Route and Query Parameters in Angular (with Signals & Inputs)

Main cover illustration for article: How to Use Route and Query Parameters in Angular (with Signals & Inputs)
Summarize with AI:

When building modern web applications in Angular, passing data across routes and components is a daily requirement. While state management stores and shared injectable services are great for global state, the URL itself is often the cleanest, most shareable way to represent component state.

Whether you need to display a specific user profile by ID (/user/123), filter a product catalog with query parameters (/products?category=tech&discount=true), or scope parameters with matrix URLs, the Angular Router provides multiple robust mechanisms to pass and read parameters.

In this tutorial, we will explore:

  1. Required Route Parameters (:id)
  2. Query Parameters (?key=value)
  3. Matrix Parameters (;key=value)
  4. Modern Signal Binding using withComponentInputBinding() and input() signals

Let's begin by understanding the differences between these parameter types.


1. Required Route Parameters (:id) 🎯

Required route parameters are embedded directly into the URL path. You define dynamic segments in your route configuration using a colon (:):

export const routes: Routes = [
  { 
    path: 'products/:id', 
    component: ProductDetailComponent 
  }
];

You can navigate to dynamic route parameters either declaratively via routerLink in your template or imperatively in TypeScript via the Router service.

<a [routerLink]="['/products', product.id]">
  View {{ product.title }}
</a>

Imperatively in TypeScript:

import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
 
@Component({
  standalone: true,
  selector: 'app-product-card',
  template: `<button (click)="openProduct(123)">View Details</button>`
})
export class ProductCardComponent {
  private readonly router = inject(Router);
 
  openProduct(id: number) {
    this.router.navigate(['/products', id]);
  }
}

The resulting URL will look like:

https://danywalls.com/products/123

Now let's look at optional Query Parameters.


2. Query Parameters (?key=value) 🔍

Query parameters appear after the question mark (?) in the URL. They are ideal for optional filtering, pagination, search terms, or sorting options that do not alter the core route path.

Passing Query Parameters

In Templates with [queryParams]:

<a [routerLink]="['/search']" [queryParams]="{ term: 'angular', page: 2 }">
  Search Angular (Page 2)
</a>

Imperatively with Router.navigate:

this.router.navigate(['/search'], {
  queryParams: { term: 'angular', page: 2 },
  queryParamsHandling: 'merge' // Preserves existing query params if needed
});

The resulting URL:

https://danywalls.com/search?term=angular&page=2

Next, let's explore Matrix Parameters.


3. Matrix Parameters (;key=value) 🧱

Matrix parameters allow you to attach optional key-value pairs directly to a specific segment of the URL path separated by semicolons (;).

<!-- In templates -->
<a [routerLink]="['/products', { color: 'blue', size: 'medium' }]">
  Filtered Products
</a>

Imperative navigation:

this.router.navigate(['/products', { color: 'blue', size: 'medium' }]);

The resulting URL:

https://danywalls.com/products;color=blue;size=medium

Now, let's look at the modern, elegant way to read all these parameters in Angular: Component Input Binding with Signals.


4. Modern Route Input Binding with Angular Signals ⚡

Historically, reading URL parameters required subscribing to ActivatedRoute.paramMap or queryParamMap Observables and manually managing subscriptions.

In modern Angular, you can bind route parameters directly to component inputs automatically using withComponentInputBinding().

Step 1: Enable withComponentInputBinding in app.config.ts

import { ApplicationConfig } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { routes } from './app.routes';
 
export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(
      routes,
      withComponentInputBinding() // 👈 Enables automatic input binding
    )
  ]
};

Step 2: Read Route & Query Parameters using Signal input()

Once enabled, any route parameter, query parameter, or router data resolved for that route will be injected directly into matching @Input or signal input() properties in your component:

import { Component, input, computed, inject } from '@angular/core';
import { ProductsService } from '../services/products.service';
 
@Component({
  standalone: true,
  selector: 'app-product-details',
  template: `
    @if (selectedProduct(); as product) {
      <article class="product-detail">
        <h2>{{ product.title }}</h2>
        <p>{{ product.description }}</p>
        <span class="price">\${{ product.price }}</span>
 
        @if (discount()) {
          <div class="badge">Special 10% Discount Applied!</div>
        }
      </article>
    }
  `
})
export class ProductDetailsComponent {
  private readonly productService = inject(ProductsService);
 
  // 1. Matches route path ':id' -> auto-injected as a Signal!
  readonly id = input.required<string>();
 
  // 2. Matches query param '?discount=true' -> auto-injected!
  readonly discount = input<string>();
 
  // 3. Reactive computed signal that automatically recalculates when 'id' changes
  readonly selectedProduct = computed(() => {
    const products = this.productService.products();
    return products.find(p => p.id === this.id());
  });
}

Why Signal Input Binding is a Game Changer:

  1. Zero boilerplate: No need to inject ActivatedRoute or manage RxJS subscriptions.
  2. Reactivity: When the URL parameter changes (e.g. user navigates from /products/1 to /products/2), the id() signal automatically updates, triggering computed signals and template re-renders cleanly.
  3. Type Safety: Works seamlessly with input.required<string>() and transform functions.

5. Alternative: Reading Parameters with ActivatedRoute & toSignal 🔄

If you need access to the full ActivatedRoute state or want to create derived signals from queryParamMap in a service:

import { Injectable, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { toSignal } from '@angular/core/rxjs-interop';
import { map } from 'rxjs';
 
@Injectable({ providedIn: 'root' })
export class SearchStateService {
  private readonly route = inject(ActivatedRoute);
 
  // Convert queryParamMap observable into a reactive Signal
  readonly searchTerm = toSignal(
    this.route.queryParamMap.pipe(
      map(params => params.get('q') ?? '')
    ),
    { initialValue: '' }
  );
}

Summary Comparison Matrix 📊

Parameter TypeURL Syntax ExampleRouter Definition Required?Best For
Route Param/products/123✅ Yes (:id)Essential entity identifiers, RESTful paths
Query Param/products?category=shoes❌ NoFilters, search queries, pagination, flags
Matrix Param/products;size=large❌ NoScoped intermediate route parameters

Recap 🛠️

Managing URL state in Angular is cleaner and faster than ever:

  • Use provideRouter(routes, withComponentInputBinding()) to automatically pipe route data into component inputs.
  • Use input() and computed() signals to reactively handle parameter updates with zero subscription management.
  • Use [queryParams] for optional filters and state that should be shareable across bookmarks.

For more modern Angular architectural patterns, check out my articles on How to Use ng-template, ng-container, and ng-content in Angular and Sharing Data Between Components in Angular!

Part of the Angular Series

These are my experiences learning and facing my daily challenges working with Angular.

View Entire Series

Frequently Asked Questions

What is the difference between Route Parameters and Query Parameters in Angular?

Route parameters (e.g. /products/:id) are required dynamic segments defined directly in your route configuration path. Query parameters (e.g. /products?category=shoes&page=2) appear after the '?' symbol, are optional, and do not need to be registered in route definitions.

How do I bind route parameters directly to component inputs in Angular?

In your application config, enable 'withComponentInputBinding()' inside 'provideRouter(routes, withComponentInputBinding())'. Then in your component, declare an input signal with the matching name (e.g. id = input<string>()). Angular automatically binds the URL parameter to the signal input.

How do I read query parameters reactively using Angular Signals?

You can inject 'ActivatedRoute' and convert its 'queryParamMap' observable into a reactive Signal using the 'toSignal()' helper from '@angular/core/rxjs-interop', or automatically receive query params as inputs when 'withComponentInputBinding()' is enabled.

What are Matrix parameters in Angular routing?

Matrix parameters are semi-colon separated key-value pairs (e.g. /products;color=red;size=large) attached directly to a specific URL path segment. They are useful for scoping optional state to a particular route hierarchy without polluting global query strings.

Related Articles

Share this article

If you found this guide helpful, consider sharing it with your team or fellow developers.


Real Software. Real Lessons.

I share the lessons I learned the hard way, so you can either avoid them or be ready when they happen.

User avatar
User avatar
User avatar
User avatar
+13K

Join 13,800+ developers and readers.

No spam ever. Unsubscribe at any time.