Advanced routing in Angular allows you to build secure, performant, and scalable applications. While basic routing covers navigation between components, advanced routing introduces concepts like lazy loading, route guards, preloading strategies, and modular route organization. This post outlines best practices to follow when designing routing for complex Angular applications, along with detailed examples and code.
1. Introduction to Advanced Routing
Angular’s router enables dynamic navigation between different views in a single-page application. As applications grow, routing complexity increases. Without proper planning, routes can become hard to manage, insecure, or slow to load.
Advanced routing best practices focus on:
- Modularity – organizing routes logically.
- Performance – using lazy loading and preloading.
- Security – guarding sensitive routes.
- Maintainability – keeping configurations centralized and consistent.
2. Keep Routes Modular and Well-Organized
A modular route structure divides the application into logical feature modules. Each module has its own routing configuration, making the application easier to maintain.
Example: Feature Modules for a Dashboard App
app/
├─ admin/
│ ├─ admin.module.ts
│ └─ admin-routing.module.ts
├─ user/
│ ├─ user.module.ts
│ └─ user-routing.module.ts
└─ app-routing.module.ts
Admin Routing Example:
// admin-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AdminDashboardComponent } from './admin-dashboard/admin-dashboard.component';
import { AdminSettingsComponent } from './admin-settings/admin-settings.component';
const routes: Routes = [
{ path: '', component: AdminDashboardComponent },
{ path: 'settings', component: AdminSettingsComponent }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AdminRoutingModule {}
Modular routing reduces clutter in the main routing file and allows independent development of modules.
3. Use Lazy Loading for Feature Modules
Lazy loading delays the loading of a module until the user navigates to it. This reduces the initial bundle size, improving load times and performance.
Lazy Loading Example:
// app-routing.module.ts
const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
},
{
path: 'user',
loadChildren: () => import('./user/user.module').then(m => m.UserModule)
}
];
Benefits:
- Faster initial app load.
- Reduced memory usage.
- Easier code splitting for large apps.
4. Protect Sensitive Routes with Guards
Route guards are classes implementing interfaces like CanActivate, CanLoad, or CanDeactivate. They prevent unauthorized access to routes.
Auth Guard Example:
// auth.guard.ts
import { Injectable } from '@angular/core';
import { CanLoad, Route, UrlSegment, Router } from '@angular/router';
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanLoad {
constructor(private router: Router) {}
canLoad(route: Route, segments: UrlSegment[]): boolean {
const token = localStorage.getItem('token');
if (!token) {
this.router.navigate(['/login']);
return false;
}
return true;
}
}
Applying the Guard to Lazy Loaded Module:
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
canLoad: [AuthGuard]
}
Guards enhance security and ensure only authorized users access sensitive parts of the application.
5. Apply Preloading for Critical Modules
While lazy loading improves performance, some modules are frequently accessed. Preloading these modules in the background balances initial load speed with navigation performance.
// app-routing.module.ts
import { PreloadAllModules } from '@angular/router';
@NgModule({
imports: [
RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
],
exports: [RouterModule]
})
export class AppRoutingModule {}
Preloading Strategies:
PreloadAllModules
– preloads all lazy-loaded modules.- Custom preloading – preload only critical modules based on logic.
6. Avoid Deeply Nested Routes for Readability
Deeply nested routes can increase complexity and make URLs harder to manage. Prefer shallow route hierarchies for better maintainability.
Bad Example:
/admin/settings/security/password/change
Better Approach:
/admin/settings
/admin/security
/admin/password
Maintain separate routing modules for different features, even if it means splitting functionality across multiple routes.
7. Keep Route Configurations Centralized and Consistent
Centralizing routing definitions improves readability and maintainability. Avoid scattering route definitions across multiple files without a structure.
App Routing Module Example:
// app-routing.module.ts
const routes: Routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
},
{
path: 'user',
loadChildren: () => import('./user/user.module').then(m => m.UserModule)
},
{ path: '**', redirectTo: 'home' }
];
Use consistent naming conventions, such as:
- Feature paths: lowercase, singular (
/admin
,/user
). - Child routes: lowercase, hyphenated (
/user-profile
).
8. Combine Guards and Lazy Loading
Combining guards with lazy loading ensures modules are not even downloaded unless the user has permission. This improves security and performance.
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
canLoad: [AuthGuard]
}
9. Use Auxiliary Routes and Named Outlets
Advanced routing sometimes requires multiple views on the same page. Angular supports named outlets for this.
Example: Sidebar Outlet:
const routes: Routes = [
{ path: 'chat', component: ChatComponent, outlet: 'sidebar' },
{ path: 'notifications', component: NotificationsComponent, outlet: 'sidebar' }
];
Navigate to multiple outlets:
this.router.navigate([{ outlets: { primary: ['home'], sidebar: ['chat'] } }]);
10. Best Practices Summary
- Modular Routing: Organize feature modules with their own routing.
- Lazy Loading: Load modules on demand to reduce initial load.
- Guards: Protect sensitive routes using CanLoad and CanActivate.
- Preloading: Preload frequently accessed modules.
- Shallow Nesting: Avoid deep route hierarchies.
- Centralized Configurations: Keep all route definitions organized in the main routing module or feature routing modules.
- Consistency: Follow naming conventions for paths, child routes, and outlets.
- Auxiliary Routes: Use named outlets for multiple view navigation.
- Testing: Ensure guards, lazy loading, and navigation work as expected.
11. Complete Example Code
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes, PreloadAllModules } from '@angular/router';
import { AuthGuard } from './guards/auth.guard';
const routes: Routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
canLoad: [AuthGuard]
},
{
path: 'user',
loadChildren: () => import('./user/user.module').then(m => m.UserModule)
},
{ path: '**', redirectTo: 'home' }
];
@NgModule({
imports: [RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })],
exports: [RouterModule]
})
export class AppRoutingModule {}
// admin-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AdminDashboardComponent } from './admin-dashboard/admin-dashboard.component';
import { AdminSettingsComponent } from './admin-settings/admin-settings.component';
const routes: Routes = [
{ path: '', component: AdminDashboardComponent },
{ path: 'settings', component: AdminSettingsComponent }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AdminRoutingModule {}
// auth.guard.ts
import { Injectable } from '@angular/core';
import { CanLoad, Route, UrlSegment, Router } from '@angular/router';
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanLoad {
constructor(private router: Router) {}
canLoad(route: Route, segments: UrlSegment[]): boolean {
const token = localStorage.getItem('token');
if (!token) {
this.router.navigate(['/login']);
return false;
}
return true;
}
}
This configuration ensures:
- Lazy loaded modules for Admin and User features.
- Route guards prevent unauthorized access.
- Preloading strategy improves navigation performance.
- Centralized, modular routing makes the app maintainable.
Leave a Reply