How to pass data between components using routes in Angular?
Techniques to Share Routes Data in Angular—How Efficient Communication Works Among Components of an Angular Application
In the world of Angular applications, components often need to share data, especially as they move from view to view. While services and state management libraries such as NgRx are often the answer, Angular's routing provides some lightweight techniques for passing information directly along routes.
If you're building a product detail page, search filter, multi-step form, or something else entirely, you need to learn how to transfer or pass data through routes. This guide demonstrates several methods with practical examples and advice on best practices.
1. The Need to Share Data via Routes
- Stateless Navigation: Prevent simple data transfer from becoming overly dependent (and inducing spaghetti code in your otherwise clean services or components).
- Bookmarkable URLs: Keeping data in the URL ensures users can return to their entry anytime without any problem.
- Lightweight: Designed for tiny, transient pieces of information like IDs and filters.
2. Passing Data via Routes
Route Parameters
Use Case: Passing key data, such as an ID (e.g., /products/123
).
Implementation
Define the Route:
// app-routing.module.ts
const routes: Routes = [
{ path: 'product/:id', component: ProductDetailComponent }
];
Navigate with the Parameter:
// product-list.component.ts
navigateToProduct(id: number) {
this.router.navigate(['/product', id]);
}
Retrieve the Parameter:
// product-detail.component.ts
import { ActivatedRoute } from '@angular/router';
export class ProductDetailComponent {
constructor(private route: ActivatedRoute) {
this.route.paramMap.subscribe(params => {
const id = params.get('id');
// Fetch product details using the ID
});
}
}
Query Parameters
Use Case: Passing optional data such as filters or sorting options (e.g., /products?category=books
).
Implementation
Navigate with Query Params:
// product-list.component.ts
applyFilter(category: string) {
this.router.navigate(['/products'], { queryParams: { category: category } });
}
Retrieve the Query Parameter:
// product-list.component.ts
this.route.queryParamMap.subscribe(params => {
const category = params.get('category');
// Filter products according to category
});
Route Data Property
Use Case: Passing static or resolved data (e.g., page titles, permissions).
Using Data in Route Configuration
Define Static Data:
// app-routing.module.ts
{
path: 'dashboard',
component: DashboardComponent,
data: { requiresAuth: true, title: 'User Dashboard' }
}
Access the Data:
// dashboard.component.ts
ngOnInit() {
this.route.data.subscribe(data => {
console.log(data.title); // Output: "User Dashboard"
});
}
Dynamic Data with Resolvers
Create a Resolver:
// product.resolver.ts
@Injectable({ providedIn: 'root' })
export class ProductResolver implements Resolve {
constructor(private productService: ProductService) {}
resolve(route: ActivatedRouteSnapshot) {
return this.productService.getProduct(route.params['id']);
}
}
Configure the Route with Resolver:
// app-routing.module.ts
{
path: 'product/:id',
component: ProductDetailComponent,
resolve: { product: ProductResolver }
}
Retrieve the Resolved Data:
// product-detail.component.ts
ngOnInit() {
this.route.data.subscribe(data => {
this.product = data.product;
});
}
State Object (NavigationExtras)
Use Case: Passing temporary or sensitive data without putting it in the URL.
Implementation
Navigate with State:
// checkout.component.ts
proceedToPayment() {
this.router.navigate(['/payment'], { state: { cartItems: this.cartItems } });
}
Retrieve the State:
// payment.component.ts
ngOnInit() {
this.cartItems = history.state.cartItems;
}
Practical Example: User Profile Editor
Scenario
Pass a user ID via route parameters and use a resolver to retrieve user data.
Route Configuration
{
path: 'profile/:userID',
component: ProfileComponent,
resolve: { user: UserResolver }
}
Create Resolver:
// user.resolver.ts
resolve(route: ActivatedRouteSnapshot) {
return this.userService.getUser(route.params['userId']);
}
Retrieve Data in Component:
// profile.component.ts
ngOnInit() {
this.route.data.subscribe(data => {
this.user = data.user;
});
}
Best Practices
- Use Route Parameters for Necessary Data: Keep URLs neat and meaningful.
- Limit State Object Size: Avoid passing large objects (risk of data loss on page reload).
- Resolvers over Route Data: Ensure data is loaded before the component is initialized.
- Encode Sensitive Information: Do not expose sensitive information in URLs.
- *Use trackBy with ngFor: Optimize performance when rendering lists from route data.
Angular provides various ways to transfer data between components through routes: from simple IDs in URLs to complex resolved data. By making the right choices in route parameters, query parameters, resolvers, and state objects, you can create flexible, user-friendly applications.
Pay attention to both security and performance, and choose the method that best fits your use case.