Report this

What is the reason for this report?

How To Use Query Parameters with Angular Router

Updated on June 10, 2026
English
How To Use Query Parameters with Angular Router

Introduction

Query parameters in Angular let you pass optional state through the URL without affecting which route is matched. Unlike route parameters, which are part of the path definition and required for a route to activate, query parameters appear after the ? character and can be absent with no effect on navigation. They are the standard mechanism for encoding filter settings, pagination state, and sort order in Angular applications.

In this tutorial, you will use a product listing scenario to learn how to set query parameters using Router.navigate and RouterLink, preserve or merge parameters across multiple navigations using queryParamsHandling, and read values in a component using ActivatedRoute. You will also learn the tradeoffs between the snapshot and observable reading strategies, and how Angular 16+ lets you bind query parameters directly to component inputs without injecting ActivatedRoute at all.

Key Takeaways

  • Query parameters are optional, appear after ? in the URL, and do not affect route matching.
  • Use queryParams with Router.navigate or [queryParams] with RouterLink to attach parameters to any navigation target.
  • queryParamsHandling: 'preserve' carries the existing query string to the next route unchanged; 'merge' combines existing parameters with new ones, overwriting duplicate keys with the new value.
  • When queryParamsHandling is omitted, Angular discards all previous query parameters on the next navigation.
  • ActivatedRoute.snapshot.queryParams is safe only when the component is always destroyed and recreated on each navigation.
  • Subscribe to ActivatedRoute.queryParams or queryParamMap for components that Angular keeps alive across navigations to the same route.
  • queryParamMap.get('key') returns null for absent parameters; always apply a null check or ?? fallback before using the value.
  • Angular 16+ supports withComponentInputBinding(), which lets you receive query parameters as @Input() properties without injecting ActivatedRoute.
  • Query parameters are ideal for search filters, pagination, and any application state that users should be able to bookmark or share via URL.

Prerequisites

To follow this tutorial, you will need:

What are query parameters in Angular?

Query parameters are key-value pairs appended to a URL after a ?, with multiple pairs separated by &. Given the URL below, order and price-range are query parameters:

http://localhost:4200/products?order=popular&price-range=not-cheap

Angular’s Router parses these pairs automatically and exposes them through ActivatedRoute. Because query parameters are not part of the route path, the route still matches /products regardless of what appears in the query string.

Query parameters vs route parameters

The following table compares query parameters with route parameters to help you choose the right tool for your use case.

Feature Query parameters Route parameters
URL appearance After ?, e.g. /products?order=popular Part of the path, e.g. /products/123
Required No, always optional Yes, required for the route to match
Route matching Do not affect route matching Must be present for the route to activate
Navigation API queryParams in NavigationExtras Path array element in Router.navigate
RouterLink binding [queryParams]="{ key: value }" Path element, e.g. [routerLink]="['/products', id]"
Typical use case Filters, sort order, pagination, shared state Resource identifiers such as IDs or slugs

The key distinction is that route parameters define the resource being loaded, while query parameters modify how that resource is presented or filtered.

When to use query parameters

Query parameters are the right choice when the value is optional (the route should activate with or without it), shareable (a user should be able to bookmark a filtered view and land in the same state), or reusable across routes (the value may need to persist during navigation to another path). Common real-world patterns include search pages (/search?q=angular), paginated lists (/products?page=2&pageSize=20), and multi-faceted filter panels (/items?category=books&sort=price).

Using query parameters with Router.navigate

To pass query parameters while navigating through code, include a queryParams object in the NavigationExtras argument to Router.navigate. NavigationExtras (imported from @angular/router) accepts queryParams, queryParamsHandling, fragment, and other options as its members.

The following example navigates to /products and appends order=popular to the URL:

import { Component } from '@angular/core';
import { Router } from '@angular/router';

@Component({ ... })
export class ProductListComponent {
  constructor(private router: Router) {}

  goProducts() {
    this.router.navigate(
      ['/products'],
      { queryParams: { order: 'popular' } }
    );
  }
}

This navigation produces a URL that resembles:

Output
http://localhost:4200/products?order=popular

To pass multiple query parameters, add more keys to the queryParams object. The example below adds both an order and a price-range parameter:

goProducts() {
  this.router.navigate(
    ['/products'],
    { queryParams: { order: 'popular', 'price-range': 'not-cheap' } }
  );
}

This navigation produces the following URL:

Output
http://localhost:4200/products?order=popular&price-range=not-cheap

Preserving or merging query parameters with queryParamsHandling

By default, Angular clears all query parameters when you navigate to a new route. The queryParamsHandling option in NavigationExtras overrides this behavior in two ways: 'preserve' keeps the current URL’s parameters entirely, and 'merge' combines the current parameters with new ones you supply.

Use 'preserve' when navigating to a related page that should inherit the current context without adding or changing anything. In this example, a user on /products?order=popular navigates to /users without losing the order parameter:

goUsers() {
  this.router.navigate(
    ['/users'],
    { queryParamsHandling: 'preserve' }
  );
}

The resulting URL retains the original parameter:

Output
http://localhost:4200/users?order=popular

Use 'merge' when you want to add or update a specific parameter without discarding the rest. The example below merges a new filter parameter with the existing order parameter:

goUsers() {
  this.router.navigate(
    ['/users'],
    {
      queryParams: { filter: 'new' },
      queryParamsHandling: 'merge'
    }
  );
}

The merged URL contains both parameters:

Output
http://localhost:4200/users?order=popular&filter=new

Note: The preserveQueryParams option was deprecated in Angular 4 and removed in Angular 8. Use queryParamsHandling: 'preserve' instead.

A practical scenario for 'merge' is a multi-step filter workflow where a user picks a category on one component and a price range on a second. Each navigation can merge its selection into the URL without overwriting the earlier choices.

The RouterLink directive supports the same queryParams and queryParamsHandling bindings as Router.navigate. Use the [queryParams] binding to set parameters and the queryParamsHandling attribute to control what happens to existing ones.

To set a query parameter in a template, bind [queryParams] to an object literal:

<a
  [routerLink]="['/products']"
  [queryParams]="{ order: 'popular' }"
>
  Products
</a>

To preserve or merge parameters during template navigation, add queryParamsHandling. The following example merges the current query string with a new filter parameter:

<a
  [routerLink]="['/users']"
  [queryParams]="{ filter: 'new' }"
  queryParamsHandling="merge"
>
  Users
</a>

Note that queryParamsHandling takes a plain string value here (not a binding), so you omit the square brackets.

Accessing query parameter values

The ActivatedRoute service provides two primary ways to read query parameters in a component: via a snapshot that captures the state at initialization, and via observables that emit whenever the URL changes. Angular 16 added a third option that binds parameters to component inputs automatically.

Reading with ActivatedRoute.snapshot.queryParams

ActivatedRoute.snapshot is an ActivatedRouteSnapshot that reflects the route state at the moment the component was created. Reading snapshot.queryParams is synchronous and requires no subscription:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({ ... })
export class ProductComponent implements OnInit {
  order: string | null = null;

  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    // Reads the value once, at component creation
    this.order = this.route.snapshot.queryParams['order'] ?? null;
    console.log(this.order);
  }
}

Running this logs the following when the URL is http://localhost:4200/products?order=popular:

Output
popular

The snapshot approach is safe when the component is always destroyed and recreated on each navigation. Its limitation is that if the same component instance stays alive while the query string changes (for example, when a user toggles a sort column without leaving the page), the snapshot will not update.

Reading with the queryParams observable

Subscribing to ActivatedRoute.queryParams gives you a stream that emits the full query parameter object every time the URL changes while the component is active. This is the correct approach for components that Angular may reuse across navigations:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { filter } from 'rxjs';

@Component({ ... })
export class ProductComponent implements OnInit {
  order: string = '';

  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    this.route.queryParams
      .pipe(filter(params => params['order'] !== undefined))
      .subscribe(params => {
        this.order = params['order'];
        console.log(this.order);
      });
  }
}

Given the URL http://localhost:4200/products?order=popular, the subscription logs:

Output
popular

The queryParams observable is the standard choice when a component needs to react to parameter changes that happen without a full page recreation. For type-safe access and multi-value support, the queryParamMap observable described next is a better fit.

Reading with queryParamMap

ActivatedRoute.queryParamMap returns an observable of a ParamMap object, which exposes typed accessor methods. This is the preferred approach for accessing individual values because ParamMap.get() returns null (not undefined) for missing keys, and ParamMap.getAll() handles parameters that appear more than once. For example, the URL /products?tag=angular&tag=router contains two tag parameters. In this case, getAll('tag') returns both values as an array.

Given the URL http://localhost:4200/products?order=popular&filter=new, you can read each parameter using paramMap.get():

this.route.queryParamMap.subscribe(params => {
  const order = params.get('order');      // 'popular'
  const filterVal = params.get('filter'); // 'new'
  const keys = params.keys;               // ['order', 'filter']
  console.log(order, filterVal, keys);
});

The subscription logs the order value, filter value, and list of active parameter names:

Output
popular new ['order', 'filter']

queryParamMap is the most future-proof way to read query parameters in Angular, since get() and getAll() make null handling explicit and parameter presence easy to check.

Snapshot vs observable: which approach to use

Use snapshot.queryParams (or snapshot.queryParamMap) when you know the component is always freshly created on each navigation and you do not need it to react to URL changes mid-lifecycle. This keeps the code simple and avoids additional reactive handling logic.

Subscribe to queryParams or queryParamMap when the component can be reused across navigations to the same route with different query strings. A list page with sortable columns is a common example: the user changes the sort order, the URL updates, but Angular may keep the same component instance active. Relying on snapshot in this scenario will leave the component displaying stale data after the second navigation. The observable approach fixes this by reacting to every URL change for as long as the component is alive.

Modern approach: binding query parameters with @Input()

Angular 16 introduced withComponentInputBinding(), a router feature that automatically binds query parameters (and other route data) to @Input() properties on the routed component. Enabling this feature removes the need to inject ActivatedRoute for straightforward read cases.

To enable it, add withComponentInputBinding() to provideRouter() in your app.config.ts:

// app.config.ts (Angular 16+, standalone application)
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { routes } from './app.routes';

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

With this feature active, declare an @Input() property whose name matches the query parameter key:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-product',
  standalone: true,
  template: `<p>Order: {{ order }}</p>`,
})
export class ProductComponent {
  // Angular binds the ?order=... query parameter to this property automatically
  @Input() order: string | undefined;
}

Angular sets order from the URL’s query string whenever the component activates or the parameter changes. For Angular 16+ projects that default to standalone components, this is the cleanest approach for read-only access to query parameters.

If you need a reactive signal instead of an @Input(), you can convert the queryParamMap observable to a signal using toSignal() from @angular/core/rxjs-interop. The inject() function (available since Angular 14) provides a clean way to access ActivatedRoute without constructor injection:

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',
  standalone: true,
  template: `<p>Order: {{ order() }}</p>`,
})
export class ProductComponent {
  private route = inject(ActivatedRoute);

  // A read-only signal that updates whenever ?order= changes
  readonly order = toSignal(
    this.route.queryParamMap.pipe(map(p => p.get('order')))
  );
}

The order property is a read-only signal compatible with Angular’s signal-based change detection introduced in Angular 16. It re-evaluates automatically when the query parameter changes, and the template re-renders without a manual subscription.

Real-world use cases

Understanding when query parameters are the right tool is as important as knowing the API. Three patterns appear across nearly every Angular application.

  • Search and filter pages encode the active filter state in the URL so that users can share or bookmark a specific view. A URL such as /items?category=books&sort=price can be pasted into a new tab and will reproduce the same filtered result. Without query parameters, that state exists only in component memory and is lost on reload.

  • Pagination uses page and pageSize parameters to let users navigate directly to a specific page and to restore their position after a browser refresh. A common convention is /products?page=2&pageSize=20, where pageSize falls back to a sensible default when absent.

  • Multi-step workflows use queryParamsHandling: 'merge' to accumulate selections across navigation steps without a shared service. A user who chooses a date range on one page and a location on the next can carry both values forward in the URL, as long as each navigation uses 'merge'.

FAQs

1. How do you pass query parameters in Angular routing?

You can pass query parameters in Angular by providing a queryParams object as part of the NavigationExtras when calling Router.navigate. For example, to add an order parameter when navigating programmatically:

this.router.navigate(['/products'], { queryParams: { order: 'popular' } });

This will produce a URL like /products?order=popular.

In templates, you use the [queryParams] binding with RouterLink to achieve the same effect:

<a [routerLink]="['/products']" [queryParams]="{ order: 'popular' }">
  Products
</a>

Both approaches append the query parameters after the route, enabling you to share optional state across the application via the URL.

To add query parameters in a template, bind the [queryParams] input of the anchor (or any element using RouterLink) to an object whose keys are your desired parameters:

<a [routerLink]="['/products']" [queryParams]="{ order: 'popular' }">
  Products
</a>

If you want to generate query parameters dynamically, bind [queryParams] to a component property (such as filterOptions), allowing the values to update in response to user actions or application state.

For example:

// In your component class
filterOptions = { order: 'popular', 'price-range': 'not-cheap' };
<a [routerLink]="['/products']" [queryParams]="filterOptions">
  Filtered Products
</a>

3. What is the difference between query parameters and route parameters in Angular?

Route parameters are included directly within the path segment of the URL. For example, /products/:id defines an id route parameter. These parameters are mandatory for the route to match correctly and must be present in the URL. Typically, route parameters are used to specify and identify a particular resource, such as the unique ID of a product.

Query parameters, on the other hand, appear after a ? in the URL and are expressed as key-value pairs. For instance, /products?order=popular uses a query parameter to indicate the sort order. Query parameters are always optional and do not influence which route is selected or matched. They are commonly used to control the display, filtering, or sorting of a resource—such as by determining the sort order or applying filters. You are free to include or omit query parameters as needed without affecting the ability of the route to match.

4. How do you pass multiple query parameters in Angular?

You can add as many query parameters as needed by including additional key-value pairs in the object. For example, adding both order and price-range:

  • Programmatic navigation:

    this.router.navigate(['/products'], {
      queryParams: { order: 'popular', 'price-range': 'not-cheap' }
    });
    
  • Template navigation:

    <a
      [routerLink]="['/products']"
      [queryParams]="{ order: 'popular', 'price-range': 'not-cheap' }"
    >
      Products
    </a>
    

Both methods generate a URL like:

/products?order=popular&price-range=not-cheap

This pattern supports unlimited optional parameters.

5. What is the difference between snapshot.queryParams and subscribing to queryParams as an observable?

When you use snapshot.queryParams, Angular reads the query parameters a single time—specifically, when the component is first created. This method is most appropriate for scenarios where the component will always be destroyed and recreated on each navigation, such as when Angular fully tears down and reinstantiates the component any time the route or URL changes. For example:

const order = this.route.snapshot.queryParams['order'];

On the other hand, subscribing to queryParams as an observable enables your component to react to changes in query parameters while it remains active. In this case, Angular does not destroy and recreate the component if only the query parameter values change. This approach is essential for dynamic interfaces, such as when providing user-editable filters or paginated content, where you want to respond to parameter changes as they occur. For example:

this.route.queryParams.subscribe(params => {
  this.order = params['order'];
});

In summary, you should use snapshot when the route always recreates the component (static approach), and use the observable subscription if the component may persist across URL changes to ensure you have up-to-date parameter values.

6. What does queryParamsHandling: 'preserve' do vs queryParamsHandling: 'merge'?

When you use queryParamsHandling: 'preserve', Angular retains the entire current query string unchanged as you navigate to a new route. For example, if you navigate from /products?order=popular to another route with the 'preserve' option, the URL for the new route will also include the original order=popular query parameter, resulting in /users?order=popular.

queryParamsHandling: 'merge', on the other hand, instructs Angular to combine the current query parameters with any new ones you specify. If you supply a new parameter that shares a key with an existing one, the new value will replace the old value. For instance, navigating from /products?order=popular to /users with queryParams: { filter: 'new' } and queryParamsHandling: 'merge' will produce the URL /users?order=popular&filter=new.

To summarize the difference:

Option Does it keep all existing parameters? Can it add or update parameters? Typical use case
preserve Yes No Retaining the full context when moving between pages
merge Yes (only replaced if keys overlap) Yes, new or existing keys can be set Adding new parameters or updating only certain keys as the user navigates

7. How do you read a specific query parameter value in an Angular component?

To read a specific query parameter value in an Angular component, you have several options depending on your requirements and Angular version:

  • If you want your component to react to changes in query parameters as they occur, you can subscribe to the ActivatedRoute.queryParamMap observable. This approach is the most robust and ensures that you receive updates whenever the query parameters change. For example:

    this.route.queryParamMap.subscribe(params => {
      const value = params.get('order'); // This will be a string value or null if the parameter is missing.
    });
    
  • If you only need to access the query parameter value once during component initialization, you may read it from the ActivatedRoute.snapshot.queryParamMap. This method captures the value at the moment the component is created:

    const value = this.route.snapshot.queryParamMap.get('order');
    
  • For applications using Angular 16 or later, and with standalone components enabled using withComponentInputBinding(), Angular can automatically assign query parameter values to component input properties. For example:

    @Input() order: string | undefined;
    

    In this case, Angular updates the order input whenever the corresponding query parameter in the URL changes.

Note that the raw queryParams object returns undefined for missing keys, while queryParamMap.get() returns null. Always check for both or use the ?? nullish coalescing operator.

8. Can query parameters cause a route mismatch or navigation failure in Angular?

No, query parameters do not influence which route Angular matches. Angular relies solely on the URL path segment to determine and activate a route, while any query parameters present in the URL (for example, ?key=value) are disregarded during the route-matching process.

As a result, Angular will match the same route for URLs like /products, /products?order=popular, or /products?foo=bar&baz=qux. The presence or absence of query parameters makes no difference to how the framework chooses and activates a route.

However, it is the developer’s responsibility to handle cases where query parameters might be missing or set to unexpected values within the component logic. You should always check whether a query parameter exists (i.e., is not null or undefined) before using its value to avoid potential runtime errors or incorrect application behavior.

For example, you can check for a parameter safely as follows:

const value = this.route.snapshot.queryParamMap.get('order');
if (value !== null) {
  // Use the value
}

Following this pattern ensures that your code gracefully handles cases where a query parameter is optional or absent.

Conclusion

In this tutorial, you used queryParams and queryParamsHandling with both Router.navigate and RouterLink to set and control query parameters, and you read parameter values using ActivatedRoute.snapshot, the queryParams observable, and queryParamMap. You also saw how Angular 16+ withComponentInputBinding() simplifies the read path for standalone components, and how toSignal() integrates query parameters with Angular’s signal system in Angular 16+.

To further expand your knowledge of Angular’s router and navigation features, check out these related tutorials:

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the author(s)

Alligator
Alligator
Author
See author profile

Alligator.io is a developer-focused resource that offers tutorials and insights on a wide range of modern front-end technologies, including Angular 2+, Vue.js, React, TypeScript, Ionic, and JavaScript.

Bradley Kouchi
Bradley Kouchi
Editor
See author profile

Former Technical Editor at DigitalOcean. Expertise in areas including Vue.js, CSS, React, and more.

Manikandan Kurup
Manikandan Kurup
Editor
Senior Technical Content Engineer I
See author profile

With over 6 years of experience in tech publishing, Mani has edited and published more than 75 books covering a wide range of data science topics. Known for his strong attention to detail and technical knowledge, Mani specializes in creating clear, concise, and easy-to-understand content tailored for developers.

Category:
Tags:

Still looking for an answer?

Was this helpful?


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

cool

you missed pipe() before .filter

Creative CommonsThis work is licensed under a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License.
Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Start building today

From GPU-powered inference and Kubernetes to managed databases and storage, get everything you need to build, scale, and deploy intelligent applications.

Dark mode is coming soon.