Using Query Parameters in Angular Routes: A Comprehensive Guide to Flexible Navigation

Query parameters in Angular routes provide a powerful way to pass optional, non-hierarchical data through URLs, enhancing the flexibility of navigation in single-page applications (SPAs). Unlike route parameters, which are part of the URL path (e.g., /product/:id), query parameters are appended to the URL (e.g., ?search=term&sort=asc) and are ideal for filtering, sorting, or tracking state. This guide offers a detailed, step-by-step exploration of using query parameters in Angular routes, covering their purpose, setup, reading, writing, and advanced use cases like preserving parameters and combining with dynamic routes. By the end, you’ll have a thorough understanding of how to leverage query parameters to build dynamic, user-friendly Angular applications.

This blog dives deeply into each concept, ensuring clarity and practical applicability while maintaining readability. We’ll incorporate internal links to related resources and provide actionable code examples. Let’s dive into using query parameters in Angular routes.


What are Query Parameters in Angular?

Query parameters are key-value pairs appended to a URL after a question mark (?), used to pass optional or contextual data to a route. For example, in the URL /products?category=electronics&sort=price, category and sort are query parameters with values electronics and price, respectively. In Angular, the Router module handles query parameters, allowing components to read and manipulate them.

Key characteristics of query parameters include:

  • Optional Nature: Unlike route parameters, they are not required for route matching.
  • Multiple Parameters: Support multiple key-value pairs separated by &.
  • State Persistence: Can be bookmarked or shared, preserving application state.
  • Use Cases: Filtering (e.g., search terms), sorting, pagination, or UI state (e.g., active tab).

Query parameters are managed via Angular’s ActivatedRoute for reading and Router for writing, often combined with dynamic routes for flexible navigation. For a foundational overview of Angular routing, see Angular Routing.


Setting Up an Angular Project with Routing

To use query parameters, we need an Angular project with the Router module. Let’s set it up.

Step 1: Create a New Angular Project

Use the Angular CLI to create a project with routing:

ng new query-params-demo --routing

Navigate to the project directory:

cd query-params-demo

The --routing flag generates a routing module (app-routing.module.ts). For more details, see Angular: Create a New Project.

Step 2: Import Required Modules

Query parameters may involve forms for user input. Update app.module.ts to include FormsModule:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, AppRoutingModule, FormsModule],
  bootstrap: [AppComponent]
})
export class AppModule {}

Step 3: Verify the Routing Module

Open app-routing.module.ts:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

Step 4: Add the Router Outlet

In app.component.html, add <router-outlet></router-outlet> to render routed components:

Query Parameters Demo

Configuring Routes and Reading Query Parameters

Let’s create a product listing application where users can filter products using query parameters (e.g., ?search=phone&category=electronics).

Step 1: Generate Components

Create components for the home page and product list:

ng generate component home
ng generate component product-list

Step 2: Define Routes

Update app-routing.module.ts:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { ProductListComponent } from './product-list/product-list.component';

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'products', component: ProductListComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

Step 3: Create a Product Service

Generate a service to simulate product data:

ng generate service product

In product.service.ts:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ProductService {
  getProducts() {
    return [
      { id: 1, name: 'Smartphone', category: 'Electronics', price: 699 },
      { id: 2, name: 'Laptop', category: 'Electronics', price: 1299 },
      { id: 3, name: 'T-Shirt', category: 'Clothing', price: 29 },
      { id: 4, name: 'Jeans', category: 'Clothing', price: 59 }
    ];
  }
}

For more on services, see Angular Services.

Step 4: Read Query Parameters

Update product-list.component.ts to read query parameters:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ProductService } from '../product.service';

@Component({
  selector: 'app-product-list',
  templateUrl: './product-list.component.html',
  styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit {
  products: any[] = [];
  searchTerm: string | null = null;
  category: string | null = null;

  constructor(private route: ActivatedRoute, private productService: ProductService) {}

  ngOnInit() {
    this.route.queryParams.subscribe(params => {
      this.searchTerm = params['search'] || null;
      this.category = params['category'] || null;
      this.filterProducts();
    });
  }

  filterProducts() {
    this.products = this.productService.getProducts().filter(product => {
      const matchesSearch = !this.searchTerm || product.name.toLowerCase().includes(this.searchTerm.toLowerCase());
      const matchesCategory = !this.category || product.category === this.category;
      return matchesSearch && matchesCategory;
    });
  }
}
  • queryParams.subscribe listens for changes to query parameters.
  • filterProducts applies filters based on search and category.

In product-list.component.html:

Products
Searching for: { { searchTerm }}
Category: { { category }}

  
    { { product.name }} ({ { product.category }}) - ${ { product.price }}
  


  No products found.

In product-list.component.css:

h2 {
  text-align: center;
}

ul {
  list-style: none;
  padding: 0;
  max-width: 600px;
  margin: 0 auto;
}

li {
  padding: 10px;
  border: 1px solid #ccc;
  margin-bottom: 5px;
  border-radius: 4px;
}

p {
  text-align: center;
  color: #007bff;
}

For more on directives like *ngIf, see Use ngIf in Templates.


Writing Query Parameters

Let’s add a search form to set query parameters programmatically.

Step 1: Add a Search Form

Update app.component.html:

Home
  Products


  
  
    All Categories
    Electronics
    Clothing
  
  Search

In app.component.ts:

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  searchTerm = '';
  category = '';

  constructor(private router: Router) {}

  onSearch() {
    this.router.navigate(['/products'], {
      queryParams: {
        search: this.searchTerm || null,
        category: this.category || null
      }
    });
  }
}

In app.component.css:

nav {
  display: flex;
  gap: 15px;
  padding: 10px;
  background-color: #f0f0f0;
}

nav a {
  text-decoration: none;
  color: #007bff;
  padding: 5px 10px;
}

nav a:hover {
  background-color: #007bff;
  color: white;
}

form {
  display: flex;
  gap: 10px;
  padding: 10px;
  justify-content: center;
}

input, select, button {
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

button {
  background-color: #007bff;
  color: white;
  border: none;
  cursor: pointer;
}

h1 {
  text-align: center;
}
  • The form uses ngModel for two-way binding.
  • onSearch navigates to /products with query parameters.

Run ng serve and test the search form to see filtered products at URLs like /products?search=phone&category=Electronics.


Preserving and Merging Query Parameters

To preserve existing query parameters during navigation, use queryParamsHandling: 'merge'.

Step 1: Add Pagination

Update product-list.component.ts:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ProductService } from '../product.service';

@Component({
  selector: 'app-product-list',
  templateUrl: './product-list.component.html'
})
export class ProductListComponent implements OnInit {
  products: any[] = [];
  searchTerm: string | null = null;
  category: string | null = null;
  page: number = 1;

  constructor(
    private route: ActivatedRoute,
    private router: Router,
    private productService: ProductService
  ) {}

  ngOnInit() {
    this.route.queryParams.subscribe(params => {
      this.searchTerm = params['search'] || null;
      this.category = params['category'] || null;
      this.page = +params['page'] || 1;
      this.filterProducts();
    });
  }

  filterProducts() {
    this.products = this.productService.getProducts().filter(product => {
      const matchesSearch = !this.searchTerm || product.name.toLowerCase().includes(this.searchTerm.toLowerCase());
      const matchesCategory = !this.category || product.category === this.category;
      return matchesSearch && matchesCategory;
    });
  }

  goToPage(page: number) {
    this.router.navigate(['/products'], {
      queryParams: { page },
      queryParamsHandling: 'merge'
    });
  }
}

In product-list.component.html:

Products
Searching for: { { searchTerm }}
Category: { { category }}
Page: { { page }}

  
    { { product.name }} ({ { product.category }}) - ${ { product.price }}
  


  No products found.


  Previous
  Next
  • queryParamsHandling: 'merge' preserves existing parameters (e.g., search, category) when updating page.
  • Pagination buttons call goToPage to update the page parameter.

For more on dynamic routes, see Create Dynamic Routes.


Combining Query Parameters with Route Parameters

Let’s combine query parameters with a dynamic route for product details.

Step 1: Generate a Product Detail Component

ng generate component product-detail

Step 2: Update Routes

In app-routing.module.ts:

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

Step 3: Update Product List Navigation

Update product-list.component.html:

{ { product.name }} ({ { product.category }}) - ${ { product.price }}

Step 4: Read Parameters in Product Detail

Update product-detail.component.ts:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ProductService } from '../product.service';

@Component({
  selector: 'app-product-detail',
  templateUrl: './product-detail.component.html'
})
export class ProductDetailComponent implements OnInit {
  product: any | null = null;
  tab: string | null = null;

  constructor(private route: ActivatedRoute, private productService: ProductService) {}

  ngOnInit() {
    this.route.paramMap.subscribe(params => {
      const id = params.get('id');
      this.product = this.productService.getProducts().find(p => p.id === +id!) || null;
    });
    this.route.queryParams.subscribe(params => {
      this.tab = params['tab'] || 'details';
    });
  }
}

In product-detail.component.html:

Product: { { product.name }}
Category: { { product.category }} - ${ { product.price }}
Active Tab: { { tab }}
Product not found.
  • The route parameter :id identifies the product.
  • The query parameter tab controls the active tab (e.g., /product/1?tab=details).

For more on route parameters, see Use Route Params in App.


Advanced Use Case: Preserving Query Parameters Across Routes

To persist query parameters when navigating between routes, use preserveQueryParams or a service.

Step 1: Create a Navigation Service

ng generate service navigation

In navigation.service.ts:

import { Injectable } from '@angular/core';
import { Router, NavigationExtras } from '@angular/router';
import { BehaviorSubject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class NavigationService {
  private queryParams = new BehaviorSubject<{ [key: string]: string }>({});

  constructor(private router: Router) {}

  updateQueryParams(params: { [key: string]: string | null }) {
    const currentParams = this.queryParams.getValue();
    const newParams = { ...currentParams, ...params };
    this.queryParams.next(newParams);
    return newParams;
  }

  navigate(commands: any[], extras: NavigationExtras = {}) {
    const queryParams = this.queryParams.getValue();
    this.router.navigate(commands, { ...extras, queryParams });
  }
}

Step 2: Use the Service

Update app.component.ts:

import { Component } from '@angular/core';
import { NavigationService } from './navigation.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  searchTerm = '';
  category = '';

  constructor(private navigationService: NavigationService) {}

  onSearch() {
    const params = {
      search: this.searchTerm || null,
      category: this.category || null
    };
    this.navigationService.updateQueryParams(params);
    this.navigationService.navigate(['/products']);
  }
}

Update product-list.component.ts:

import { NavigationService } from '../navigation.service';

goToPage(page: number) {
  this.navigationService.navigate(['/products'], {
    queryParams: { page },
    queryParamsHandling: 'merge'
  });
}

This service centralizes query parameter management, ensuring consistency across routes. For more on observables, see Angular Observables.


FAQs

What are query parameters in Angular?

Query parameters are optional key-value pairs appended to URLs (e.g., ?search=term) to pass data like filters or UI state, accessible via ActivatedRoute.

How do I read query parameters in a component?

Use ActivatedRoute’s queryParams observable or snapshot.queryParams to retrieve parameters like params['search'].

How do I set query parameters programmatically?

Use the Router service’s navigate method with a queryParams object, optionally with queryParamsHandling: 'merge' to preserve existing parameters.

What’s the difference between query and route parameters?

Query parameters are optional and appended to URLs (e.g., ?key=value), while route parameters are required parts of the path (e.g., :id in /product/:id).

How do I preserve query parameters across routes?

Use queryParamsHandling: 'merge' in router.navigate or manage parameters with a service to persist them during navigation.


Conclusion

Using query parameters in Angular routes provides a flexible way to pass optional data, enabling dynamic filtering, sorting, and state management in SPAs. This guide covered setting up query parameters, reading and writing them, preserving parameters, and combining them with route parameters, offering a solid foundation for building user-friendly applications.

To deepen your knowledge, explore related topics like Create Dynamic Routes for parameter-based navigation, Use Router Guards for Routes for access control, or Create Responsive Layout for better UI design. With query parameters, you can craft intuitive, scalable Angular applications tailored to your needs.