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.