Angular Routing and Navigation: Complete Guide
Angular Router enables navigation between views and manages application state. Learn how to configure routes, implement lazy loading, create route guards, handle route parameters, and build enterprise navigation patterns for production Angular applications.
Building a single-page application sounds simple until you need to handle routing. I've seen Angular applications where every route was eagerly loaded (making the initial bundle huge), routes weren't protected (allowing unauthorized access), and navigation was handled inconsistently. When I learned how to properly configure Angular Router, it transformed how I build applications.
Angular Router is a powerful routing framework that enables navigation between views based on URL changes. It supports lazy loading (loading modules only when needed), route guards (protecting routes based on authentication or authorization), route parameters (dynamic segments like /products/:id), query parameters (for filtering and searching), and nested routes (for complex layouts). Understanding these features is crucial for building scalable Angular applications.
In this guide, I'll show you how I configure routing in production Angular applications. We'll cover basic route configuration, lazy loading modules (to reduce initial bundle size), route guards (CanActivate, CanDeactivate, CanLoad), route parameters and query parameters, nested routes and child routes, programmatic navigation, and route resolvers (for prefetching data). I'll also share patterns I've learned for organizing routes in large applications and handling common routing scenarios.
Basic Route Configuration
Set up routing in your app routing module:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DashboardComponent } from './dashboard/dashboard.component';
import { BusinessListComponent } from './business/business-list.component';
import { UserListComponent } from './users/user-list.component';
const routes: Routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent },
{ path: 'business', component: BusinessListComponent },
{ path: 'users', component: UserListComponent },
{ path: '**', redirectTo: '/dashboard' } // Wildcard route
];
@NgModule({
imports: [RouterModule.forRoot(routes, {
relativeLinkResolution: 'legacy',
useHash: false
})],
exports: [RouterModule]
})
export class AppRoutingModule { }Lazy Loading Modules
Implement lazy loading for better performance:
const routes: Routes = [
{
path: 'business',
loadChildren: () => import('./business/business.module')
.then(m => m.BusinessModule)
},
{
path: 'users',
loadChildren: () => import('./users/users.module')
.then(m => m.UsersModule)
},
{
path: 'sites',
loadChildren: () => import('./site/site.module')
.then(m => m.SiteModule)
}
];
// In feature module (e.g., business.module.ts)
const businessRoutes: Routes = [
{ path: '', component: BusinessListComponent },
{ path: ':id', component: BusinessDetailsComponent }
];
@NgModule({
imports: [RouterModule.forChild(businessRoutes)],
exports: [RouterModule]
})
export class BusinessModule { }Route Guards
Protect routes with guards:
import { Injectable } from '@angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AuthService } from '../auth/auth.service';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthService,
private router: Router
) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> | Promise<boolean> | boolean {
return this.authService.isAuthenticated.pipe(
map(isAuthenticated => {
if (!isAuthenticated) {
localStorage.setItem('returnUrl', state.url);
this.authService.Logout();
return false;
}
return true;
})
);
}
}
// Use in routes
const routes: Routes = [
{
path: 'business',
canActivate: [AuthGuard],
loadChildren: () => import('./business/business.module').then(m => m.BusinessModule)
}
];Route Parameters
Access route parameters:
// Route definition
{ path: 'business/:id', component: BusinessDetailsComponent }
// Component
import { ActivatedRoute, Router } from '@angular/router';
export class BusinessDetailsComponent implements OnInit {
businessId: number;
constructor(
private route: ActivatedRoute,
private router: Router
) {}
ngOnInit(): void {
// Snapshot (one-time)
this.businessId = +this.route.snapshot.params['id'];
// Observable (for dynamic updates)
this.route.params.subscribe(params => {
this.businessId = +params['id'];
this.loadBusiness();
});
}
navigateToEdit(): void {
this.router.navigate(['/business', this.businessId, 'edit']);
}
}Query Parameters
Handle query parameters:
// Navigate with query params
this.router.navigate(['/business'], {
queryParams: { page: 1, size: 10, search: 'term' }
});
// Read query params
this.route.queryParams.subscribe(params => {
const page = params['page'] || 1;
const size = params['size'] || 10;
this.loadData(page, size);
});
// Preserve query params on navigation
this.router.navigate(['/users'], {
queryParamsHandling: 'preserve'
});Navigation Methods
Different ways to navigate:
import { Router } from '@angular/router';
export class MyComponent {
constructor(private router: Router) {}
// Programmatic navigation
navigateToBusiness(): void {
this.router.navigate(['/business']);
}
navigateWithParams(): void {
this.router.navigate(['/business', 123]);
}
navigateRelative(): void {
this.router.navigate(['../users'], { relativeTo: this.route });
}
// Template navigation
// <a routerLink="/business">Business</a>
// <a [routerLink]="['/business', businessId]">Details</a>
// <a [routerLink]="['/business']" [queryParams]="{page: 1}">With Query</a>
}Best Practices
- Use lazy loading for feature modules to reduce initial bundle size
- Implement route guards for authentication and authorization
- Use route data to pass static data to components
- Handle route parameters with observables for dynamic updates
- Use query parameters for optional, filterable data
- Implement proper error handling for route navigation
- Use RouterLink directive in templates for declarative navigation
- Store return URLs for redirecting after authentication
- Use route resolvers to prefetch data before route activation
- Implement proper route organization and naming conventions
Conclusion
Angular Router provides a comprehensive routing solution for building single-page applications. With lazy loading, route guards, and navigation patterns, you can create scalable, maintainable routing architectures for enterprise Angular applications.