Angular Navigation
Navigation is a fundamental aspect of any web application. In Angular applications, navigation allows users to move between different views without requiring a full page reload, creating a smoother user experience. This tutorial will guide you through implementing navigation in Angular applications using Angular's powerful routing capabilities.
Introduction to Angular Navigation
Angular provides a robust Router module that enables navigation between views as users perform application tasks. It interprets a browser URL as an instruction to navigate to a client-generated view, and allows you to pass optional parameters that help define the view.
The Router maps URLs to components rather than pages, which is essential for Single Page Applications (SPAs). This approach provides several advantages:
- Users experience faster transitions between views
- The application maintains state across views
- The application can load data in the background while displaying views
Setting Up Basic Navigation
Prerequisites
Before implementing navigation, ensure you have:
- An Angular application set up
- The Angular Router module installed (it comes by default in most Angular applications)
Step 1: Create Components for Navigation
First, let's create some components that we'll navigate between:
ng generate component home
ng generate component about
ng generate component contact
Step 2: Configure Routes
In your app-routing.module.ts
file, define routes that map URLs to components:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { ContactComponent } from './contact/contact.component';
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: 'contact', component: ContactComponent },
{ path: '**', redirectTo: '' } // Wildcard route for a 404 page
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Step 3: Add Router Outlet
In your app.component.html
, add the router outlet where components will be displayed when navigated to:
<div class="container">
<!-- Navigation menu will go here -->
<router-outlet></router-outlet>
</div>
Creating a Navigation Menu
Now let's create a navigation menu to allow users to move between components:
Basic Navigation Links
In your app.component.html
, add navigation links above the router outlet:
<div class="container">
<nav>
<ul>
<li><a routerLink="/">Home</a></li>
<li><a routerLink="/about">About</a></li>
<li><a routerLink="/contact">Contact</a></li>
</ul>
</nav>
<router-outlet></router-outlet>
</div>
Adding Active Route Styling
To highlight the current active route, use routerLinkActive
:
<nav>
<ul>
<li><a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">Home</a></li>
<li><a routerLink="/about" routerLinkActive="active">About</a></li>
<li><a routerLink="/contact" routerLinkActive="active">Contact</a></li>
</ul>
</nav>
Add some CSS to style the active link:
.active {
color: #4CAF50;
font-weight: bold;
}
Programmatic Navigation
Sometimes you need to navigate between routes in your component code rather than through user clicks on links. Angular provides the Router service for this purpose:
Navigating from Component Code
import { Component } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent {
constructor(private router: Router) {}
navigateToContact() {
// Navigate to the contact page
this.router.navigate(['/contact']);
}
navigateToAboutWithQueryParams() {
// Navigate with query parameters
this.router.navigate(['/about'], {
queryParams: { section: 'team' }
});
}
}
In the component template:
<div>
<h1>Welcome to the Home Page</h1>
<button (click)="navigateToContact()">Contact Us</button>
<button (click)="navigateToAboutWithQueryParams()">About Our Team</button>
</div>
Handling Route Parameters
Route parameters allow you to pass data as part of the URL:
Configure Parameterized Routes
Update your routing module to include parameter routes:
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: 'contact', component: ContactComponent },
{ path: 'product/:id', component: ProductDetailComponent },
{ path: '**', redirectTo: '' }
];
Creating Links with Parameters
<a [routerLink]="['/product', 1]">Product 1</a>
<a [routerLink]="['/product', 2]">Product 2</a>
<a [routerLink]="['/product', 3]">Product 3</a>
Accessing Parameters in Components
Create a ProductDetailComponent:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-product-detail',
template: `
<div>
<h2>Product Details</h2>
<p>Showing details for product ID: {{productId}}</p>
</div>
`
})
export class ProductDetailComponent implements OnInit {
productId: string;
constructor(private route: ActivatedRoute) { }
ngOnInit() {
// Snapshot approach (doesn't update if params change while component is active)
this.productId = this.route.snapshot.paramMap.get('id');
// Alternative: Observable approach (reacts to param changes)
this.route.paramMap.subscribe(params => {
this.productId = params.get('id');
});
}
}
Nested Navigation
For more complex applications, you may need nested routes where child components are displayed within parent components:
Configure Child Routes
const routes: Routes = [
{
path: 'admin',
component: AdminComponent,
children: [
{ path: '', component: AdminDashboardComponent },
{ path: 'users', component: AdminUsersComponent },
{ path: 'settings', component: AdminSettingsComponent }
]
},
// other routes...
];
Parent Component Template
<div class="admin-panel">
<h2>Admin Panel</h2>
<nav>
<ul>
<li><a routerLink="/admin" [routerLinkActiveOptions]="{exact: true}" routerLinkActive="active">Dashboard</a></li>
<li><a routerLink="/admin/users" routerLinkActive="active">Users</a></li>
<li><a routerLink="/admin/settings" routerLinkActive="active">Settings</a></li>
</ul>
</nav>
<!-- Child components will be displayed here -->
<router-outlet></router-outlet>
</div>
Real-world Example: Building a Product Catalog
Let's build a simple product catalog to demonstrate navigation in a realistic scenario:
1. Create Required Components
ng generate component products
ng generate component product-list
ng generate component product-detail
ng generate component product-category
2. Set Up Routes
const routes: Routes = [
{ path: '', redirectTo: '/products', pathMatch: 'full' },
{
path: 'products',
component: ProductsComponent,
children: [
{ path: '', component: ProductListComponent },
{ path: 'category/:category', component: ProductCategoryComponent },
{ path: ':id', component: ProductDetailComponent }
]
},
// other routes...
];
3. Product Service
import { Injectable } from '@angular/core';
export interface Product {
id: number;
name: string;
category: string;
price: number;
description: string;
}
@Injectable({
providedIn: 'root'
})
export class ProductService {
private products: Product[] = [
{ id: 1, name: 'Laptop', category: 'electronics', price: 999.99, description: 'Powerful laptop for all your needs' },
{ id: 2, name: 'Smartphone', category: 'electronics', price: 699.99, description: 'Latest smartphone with amazing features' },
{ id: 3, name: 'Desk Chair', category: 'furniture', price: 199.99, description: 'Comfortable chair for your workspace' },
{ id: 4, name: 'Coffee Table', category: 'furniture', price: 149.99, description: 'Stylish coffee table for your living room' },
{ id: 5, name: 'Headphones', category: 'electronics', price: 99.99, description: 'High-quality wireless headphones' }
];
constructor() { }
getAllProducts(): Product[] {
return this.products;
}
getProductById(id: number): Product | undefined {
return this.products.find(product => product.id === id);
}
getProductsByCategory(category: string): Product[] {
return this.products.filter(product => product.category === category);
}
getCategories(): string[] {
return [...new Set(this.products.map(product => product.category))];
}
}
4. Products Component
import { Component } from '@angular/core';
import { ProductService } from '../product.service';
@Component({
selector: 'app-products',
template: `
<div class="products-container">
<div class="sidebar">
<h3>Categories</h3>
<ul>
<li><a routerLink="/products" [routerLinkActiveOptions]="{exact: true}" routerLinkActive="active">All Products</a></li>
<li *ngFor="let category of categories">
<a [routerLink]="['/products/category', category]" routerLinkActive="active">
{{category | titlecase}}
</a>
</li>
</ul>
</div>
<div class="main-content">
<router-outlet></router-outlet>
</div>
</div>
`,
styles: [`
.products-container { display: flex; }
.sidebar { width: 200px; padding: 20px; background: #f5f5f5; }
.main-content { flex-grow: 1; padding: 20px; }
.active { font-weight: bold; color: #4CAF50; }
`]
})
export class ProductsComponent {
categories: string[];
constructor(private productService: ProductService) {
this.categories = this.productService.getCategories();
}
}
5. Product List Component
import { Component } from '@angular/core';
import { Product, ProductService } from '../product.service';
@Component({
selector: 'app-product-list',
template: `
<h2>All Products</h2>
<div class="product-grid">
<div *ngFor="let product of products" class="product-card">
<h3>{{product.name}}</h3>
<p>{{product.category | titlecase}}</p>
<p>{{product.price | currency}}</p>
<a [routerLink]="['/products', product.id]">View Details</a>
</div>
</div>
`,
styles: [`
.product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 20px; }
.product-card { border: 1px solid #ddd; padding: 15px; border-radius: 5px; }
`]
})
export class ProductListComponent {
products: Product[];
constructor(private productService: ProductService) {
this.products = this.productService.getAllProducts();
}
}
6. Product Category Component
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Product, ProductService } from '../product.service';
@Component({
selector: 'app-product-category',
template: `
<h2>{{category | titlecase}} Products</h2>
<div class="product-grid" *ngIf="products.length > 0; else noProducts">
<div *ngFor="let product of products" class="product-card">
<h3>{{product.name}}</h3>
<p>{{product.price | currency}}</p>
<a [routerLink]="['/products', product.id]">View Details</a>
</div>
</div>
<ng-template #noProducts>
<p>No products found in this category.</p>
</ng-template>
`,
styles: [`
.product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 20px; }
.product-card { border: 1px solid #ddd; padding: 15px; border-radius: 5px; }
`]
})
export class ProductCategoryComponent implements OnInit {
products: Product[] = [];
category: string = '';
constructor(
private route: ActivatedRoute,
private productService: ProductService
) { }
ngOnInit(): void {
this.route.paramMap.subscribe(params => {
this.category = params.get('category') || '';
this.products = this.productService.getProductsByCategory(this.category);
});
}
}
7. Product Detail Component
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Product, ProductService } from '../product.service';
@Component({
selector: 'app-product-detail',
template: `
<div *ngIf="product; else notFound">
<h2>{{product.name}}</h2>
<p><strong>Category:</strong> {{product.category | titlecase}}</p>
<p><strong>Price:</strong> {{product.price | currency}}</p>
<p><strong>Description:</strong> {{product.description}}</p>
<button (click)="goBack()">Back to Products</button>
</div>
<ng-template #notFound>
<div>
<h2>Product Not Found</h2>
<p>We couldn't find the product you're looking for.</p>
<button (click)="goBack()">Back to Products</button>
</div>
</ng-template>
`
})
export class ProductDetailComponent implements OnInit {
product: Product | undefined;
constructor(
private route: ActivatedRoute,
private router: Router,
private productService: ProductService
) { }
ngOnInit(): void {
this.route.paramMap.subscribe(params => {
const id = Number(params.get('id'));
this.product = this.productService.getProductById(id);
});
}
goBack(): void {
this.router.navigate(['/products']);
}
}
This example demonstrates:
- Main navigation routes
- Child routes
- Route parameters
- Programmatic navigation
- Active route styling
- Navigation between different product views
Navigation Guards
Navigation guards allow you to control access to certain routes. Some common guards include:
- CanActivate: Controls if a route can be accessed
- CanDeactivate: Controls if a user can navigate away from a route
- Resolve: Pre-fetch data before activating a route
Here's a basic example of a CanActivate
guard to protect admin routes:
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(): boolean {
if (this.authService.isLoggedIn()) {
return true;
}
this.router.navigate(['/login']);
return false;
}
}
Then apply it to your routes:
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: 'contact', component: ContactComponent },
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard],
children: [
// Admin routes...
]
}
];
Summary
In this tutorial, you've learned how to implement navigation in Angular applications:
- Setting up a basic router configuration
- Creating navigation menus with RouterLink directives
- Styling active routes with RouterLinkActive
- Implementing programmatic navigation using the Router service
- Working with route parameters to create dynamic routes
- Creating nested navigation with parent and child routes
- Building a complete product catalog with navigation
- Implementing route guards to protect routes
Angular's Router is a powerful tool that allows you to create sophisticated navigation patterns for your applications. By mastering navigation in Angular, you can create smooth, intuitive user experiences in your single-page applications.
Exercises
- Create a simple blog application with routes for a home page, blog list, and blog detail pages using route parameters.
- Implement a navigation guard that prevents users from leaving a form page if they have unsaved changes.
- Create a multi-level menu with nested routes that shows different sections and subsections of content.
- Add query parameters to a search feature that preserves search terms in the URL.
- Create a breadcrumb component that shows the hierarchical path of nested routes.
Additional Resources
- Angular Router Official Documentation
- Angular Router In-Depth Guide
- Route Guards and Resolvers
- Angular University: Angular Router Course
Happy navigating with Angular!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)