1. Introduction
Routing is one of the most powerful features of Angular. It allows developers to create single-page applications that can navigate between different views without reloading the page. The Angular Router is responsible for interpreting browser URLs and displaying the corresponding view to the user.
While basic routing is enough for small applications, as your app grows, you need advanced routing techniques to improve performance, security, and scalability. These techniques help manage complex navigation structures, optimize lazy loading, secure routes with guards, and control module preloading.
In this post, you’ll learn about lazy loading, route guards, and preloading strategies, along with practical examples and best practices.
2. Understanding Angular Routing
Angular routing is based on the concept of defining routes — each route maps a path to a specific component or module. The Router enables dynamic navigation without full page reloads.
Example – Basic Routing Setup:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
Here, the RouterModule connects paths (/
and /about
) with components.
As applications grow, however, you can’t load everything at once. You need to manage routing efficiently — and that’s where advanced techniques come in.
3. Why Advanced Routing Is Important
Large Angular applications often consist of multiple modules, hundreds of components, and complex navigation structures. Without optimization, routing can affect load times and user experience.
Key Challenges in Large Apps:
- Loading all modules at startup increases bundle size.
- Sensitive routes need protection from unauthorized users.
- Some modules should preload for faster access.
Advanced Routing Techniques Solve These Challenges:
- Lazy Loading: Load modules only when needed.
- Route Guards: Control access to specific routes.
- Preloading Strategies: Load certain routes in the background for speed.
4. Lazy Loading Modules
What Is Lazy Loading?
Lazy loading is the technique of loading feature modules on demand — only when the user navigates to a specific route. It reduces the initial bundle size, improving startup performance.
Instead of loading the entire app upfront, Angular loads core modules first and fetches other modules as needed.
Implementing Lazy Loading
Let’s create a lazy-loaded module called AdminModule
.
Step 1 – Generate a Feature Module:
ng generate module admin --route admin --module app.module
This command automatically sets up lazy loading in your AppRoutingModule
.
Step 2 – Verify Routing Configuration:
const routes: Routes = [
{
path: 'admin',
loadChildren: () =>
import('./admin/admin.module').then(m => m.AdminModule)
}
];
Step 3 – Admin Module Setup:
@NgModule({
declarations: [AdminDashboardComponent],
imports: [CommonModule, RouterModule.forChild([
{ path: '', component: AdminDashboardComponent }
])]
})
export class AdminModule {}
When a user visits /admin
, Angular loads the AdminModule
dynamically.
Benefits of Lazy Loading
- Faster initial load time.
- Better app scalability.
- Reduced bandwidth consumption.
- Independent module updates.
Common Mistakes
- Importing lazy modules into
AppModule
(defeats the purpose). - Forgetting to use
forChild()
instead offorRoot()
in feature modules.
5. Route Guards
Overview
Route Guards are classes that determine whether navigation to a route should be allowed or blocked. They are essential for securing routes, confirming navigation, or loading data before entering a view.
Angular provides several guard interfaces:
CanActivate
– controls access before entering a route.CanDeactivate
– prevents leaving a route.CanLoad
– controls if a lazy-loaded module should load.Resolve
– pre-fetches data before route activation.CanActivateChild
– guards child routes.
Implementing CanActivate Guard
Step 1 – Create an Auth Guard:
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 auth: AuthService, private router: Router) {}
canActivate(): boolean {
if (this.auth.isLoggedIn()) {
return true;
} else {
this.router.navigate(['/login']);
return false;
}
}
}
Step 2 – Apply Guard to a Route:
const routes: Routes = [
{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] }
];
This ensures that only logged-in users can access the dashboard.
Implementing CanDeactivate Guard
CanDeactivate
guards prevent users from leaving a route accidentally when unsaved changes exist.
Example:
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { EditableComponent } from './editable.component';
@Injectable({ providedIn: 'root' })
export class UnsavedChangesGuard implements CanDeactivate<EditableComponent> {
canDeactivate(component: EditableComponent): boolean {
return component.isSaved || confirm('You have unsaved changes. Leave anyway?');
}
}
Usage:
{ path: 'edit', component: EditableComponent, canDeactivate: [UnsavedChangesGuard] }
This prevents data loss by confirming user navigation.
6. Preloading Strategies
Why Use Preloading?
Lazy loading improves performance but can delay access to certain routes when users first navigate to them. Preloading strategies solve this by loading modules in the background after the app starts.
Angular includes two built-in strategies:
NoPreloading
(default)PreloadAllModules
You can also define custom strategies.
Using PreloadAllModules
Example:
import { RouterModule, Routes, PreloadAllModules } from '@angular/router';
const routes: Routes = [
{ path: 'users', loadChildren: () => import('./users/users.module').then(m => m.UsersModule) },
{ path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) }
];
@NgModule({
imports: [RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })],
exports: [RouterModule]
})
export class AppRoutingModule {}
This approach preloads all lazy-loaded modules in the background for smoother user experience.
Creating a Custom Preloading Strategy
You can control which modules preload and which don’t.
Custom Strategy Example:
import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class SelectivePreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
return route.data && route.data['preload'] ? load() : of(null);
}
}
Route Example:
const routes: Routes = [
{ path: 'dashboard', loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule), data: { preload: true } },
{ path: 'reports', loadChildren: () => import('./reports/reports.module').then(m => m.ReportsModule) }
];
Only the dashboard module preloads; reports load lazily.
7. Combining Lazy Loading and Preloading
You can combine both lazy loading and preloading for optimal performance:
- Use lazy loading for large modules.
- Use preloading for frequently visited or critical routes.
This ensures minimal initial bundle size while providing fast access to important sections.
8. Structuring Routes in Large Applications
To maintain clarity, organize your routes by feature modules and use nested child routes when necessary.
Example:
const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
canActivate: [AuthGuard],
children: [
{ path: 'users', component: AdminUsersComponent },
{ path: 'settings', component: AdminSettingsComponent }
]
}
];
Avoid deeply nested routes beyond 2–3 levels to maintain readability.
9. Lazy Loading Shared Modules
If multiple modules depend on shared functionality, use a shared module pattern.
Example Shared Module:
@NgModule({
declarations: [HeaderComponent, FooterComponent],
exports: [HeaderComponent, FooterComponent],
imports: [CommonModule]
})
export class SharedModule {}
Import SharedModule
only where needed — not lazily loaded, but keeps dependencies minimal.
10. Handling 404 and Fallback Routes
Always define a wildcard route at the end for unknown paths.
Example:
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: '**', component: NotFoundComponent }
];
This ensures users are redirected to a meaningful page instead of seeing a blank screen.
11. Route Resolvers
A resolver loads data before navigation occurs, ensuring that the component has necessary data on load.
Example:
@Injectable({ providedIn: 'root' })
export class UserResolver implements Resolve<User> {
constructor(private userService: UserService) {}
resolve(): Observable<User> {
return this.userService.getCurrentUser();
}
}
Route Usage:
{ path: 'profile', component: ProfileComponent, resolve: { user: UserResolver } }
Resolvers improve user experience by preventing empty screens while waiting for data.
12. Best Practices for Advanced Routing
- Use lazy loading for all feature modules.
- Keep AppModule small and lightweight.
- Secure sensitive routes using guards.
- Implement custom preloading for high-priority modules.
- Organize routes by features and hierarchy.
- Always include a fallback route.
- Avoid hardcoding routes — use routerLink for navigation.
- Manage state between routes using services or NgRx.
13. Example: Combining All Techniques
Below is an example combining lazy loading, guards, and preloading strategies.
app-routing.module.ts
const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule),
canActivate: [AuthGuard],
data: { preload: true }
},
{
path: 'settings',
loadChildren: () => import('./settings/settings.module').then(m => m.SettingsModule)
},
{ path: '**', component: NotFoundComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes, { preloadingStrategy: SelectivePreloadingStrategy })],
exports: [RouterModule]
})
export class AppRoutingModule {}
This configuration ensures:
- Modules load only when needed.
- Authorized users access restricted areas.
- Critical routes preload in the background.
Leave a Reply