As Angular applications grow in size and complexity, performance and security become crucial. One of the most effective ways to address both concerns is by leveraging lazy loading and route guards in your Angular routing configuration.
Lazy loading enables you to load feature modules only when they’re needed, thereby reducing the initial load time and enhancing application speed. On the other hand, route guards help protect access to specific routes based on conditions such as user authentication or roles, thereby improving the overall security and user experience of your application.
Together, these tools offer a powerful way to build scalable, modular, and secure Angular apps.
In this tutorial, you’ll learn:
-
How lazy loading works in Angular and how to implement it using feature modules.
-
How to use route guards like
CanActivate
andCanLoad
to protect routes. -
Best practices for organizing routes and securing your application.
-
Real-world examples that combine lazy loading with authentication and role-based access control.
By the end of this tutorial, you’ll be able to optimize your Angular app for performance and security using lazy loading and route guards, ensuring users get the fastest and safest experience possible.
What is Lazy Loading in Angular?
In Angular, lazy loading is a design pattern that delays the loading of modules until they are needed. This improves the initial load time of your application by only loading the essential parts, while feature modules are loaded on demand when the user navigates to a specific route.
Eager Loading vs Lazy Loading
By default, Angular uses eager loading, where all modules are bundled and loaded upfront when the application starts. While this works well for small applications, it can cause performance issues as your app grows.
In contrast, lazy loading loads feature modules dynamically as users navigate through the application. This keeps the initial bundle smaller and speeds up the bootstrapping process.
Loading Strategy | Description | Pros | Cons |
---|---|---|---|
Eager Loading | Loads all modules at startup | Simple to implement | Slower initial load time |
Lazy Loading | Loads modules on demand | Faster startup, better performance | Requires careful routing setup |
Use Cases for Lazy Loading
Lazy loading is ideal for:
-
Admin or dashboard areas are only accessible to certain users
-
Large feature modules like reports, analytics, or settings
-
Multi-role applications with separate modules per user type
How Angular Implements Lazy Loading
Angular implements lazy loading using the loadChildren
property in the route configuration. This tells the Angular router to load a module only when its route is activated.
Here’s a basic example:
const routes: Routes = [
{
path: 'admin',
loadChildren: () =>
import('./admin/admin.module').then((m) => m.AdminModule),
},
];
In this example, the AdminModule
is not loaded until the user navigates to /admin
.
Setting Up an Angular App
To demonstrate lazy loading and route guards, we’ll build a sample Angular application with multiple modules and protected routes.
Prerequisites
Make sure you have the following installed:
-
Node.js (v20 or later)
-
Angular CLI (v20 or later)
Install Angular CLI globally if you haven’t:
npm install -g @angular/cli
Step 1: Create a New Angular Project
ng new angular-lazy-guards --routing --style=css
cd angular-lazy-guards
The --routing
flag automatically creates a routing module, which is essential for lazy loading.
Step 2: Generate Feature Routes with Standalone Components
We’ll create two feature sections:
-
admin
(protected area) -
user
(public or default area)
Each section will have a dashboard component and its own routing.
ng generate component admin/dashboard --standalone --flat
ng generate component user/dashboard --standalone --flat
Step 3: Manually Create Lazy Route Files
Since the CLI expects a --module
(even in standalone mode), we’ll skip the --route
flag and instead manually create the routing setup.
Create src/app/admin.routes.ts
import { Routes } from '@angular/router';
import { Dashboard } from './admin/dashboard';
export default [
{
path: '',
component: Dashboard,
},
] satisfies Routes;
Create src/app/user.routes.ts
import { Routes } from '@angular/router';
import { Dashboard } from './user/dashboard';
export default [
{
path: '',
component: Dashboard,
},
] satisfies Routes;
Step 4: Configure Main Routing with Lazy Loading
Open src/app/app.routes.ts
and update it like this:
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./admin.routes'),
},
{
path: 'user',
loadChildren: () => import('./user.routes'),
},
{
path: '',
redirectTo: 'user',
pathMatch: 'full',
},
];
⚠️ You don’t need to use
NgModules
or the--module
flag when manually creating lazy-loaded routes like this — just make sure to return aRoutes
array from the imported file.
What are Route Guards?
In Angular, route guards are interfaces that allow you to control access to specific routes in your application. They act as gatekeepers, deciding whether a user can navigate to a route, leave a route, or even load a lazy-loaded module.
Guards are especially useful when combined with lazy loading, ensuring that unauthorized users cannot even load certain parts of your application.
Types of Angular Route Guards
Angular provides several built-in guard interfaces:
Guard Interface | Description |
---|---|
CanActivate |
Checks if the route can be activated (navigated to). |
CanDeactivate |
Checks if the user can leave the current route. |
CanLoad |
Prevents the loading of lazy-loaded modules. |
CanActivateChild |
Checks if child routes can be activated. |
Resolve |
Fetches data before the route is activated. |
For this tutorial, we’ll focus on the two most important for lazy loading:
-
CanActivate
: Controls if a user can access a specific route. -
CanLoad
: Prevents loading of a lazy-loaded route module if the condition is not met.
Why Use Guards?
Use guards to:
-
Protect admin or private routes from unauthorized users.
-
Prevent loading of large feature modules for users without access.
-
Redirect unauthenticated users to a login or error page.
-
Preload data before route activation using
Resolve
.
Basic Flow
When a user navigates to a guarded route:
-
Angular calls the guard service (e.g.,
AuthGuard
). -
The guard returns:
-
true
: allow access. -
false
: deny access. -
A
UrlTree
: redirect to another route.
-
-
If it's a
CanLoad
guard, Angular will not even load the route module if access is denied.
In the next section, we’ll build actual route guard services and apply them to the lazy-loaded routes in your Angular 20 standalone app.
Creating and Using Route Guards
Now that you understand how route guards work, let’s build them step by step using Angular 20’s standalone architecture.
We’ll create:
-
An
AuthGuard
usingCanActivate
-
A
CanLoadGuard
to block lazy-loaded routes -
A simple
AuthService
to simulate authentication
Step 1: Create the Auth Service
This service will simulate login and user status.
src/app/auth.service.ts
import { Injectable, computed, signal } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class AuthService {
private _isLoggedIn = signal(false); // simulate login status
readonly isLoggedIn = computed(() => this._isLoggedIn());
login() {
this._isLoggedIn.set(true);
}
logout() {
this._isLoggedIn.set(false);
}
}
Step 2: Create the Auth Guard (CanActivate
)
This guard will protect the route after it's loaded.
src/app/auth.guard.ts
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';
export const authGuard: CanActivateFn = () => {
const auth = inject(AuthService);
const router = inject(Router);
return auth.isLoggedIn() || router.createUrlTree(['/user']);
};
Step 3: Create the CanLoad Guard
This guard will prevent lazy loading of the entire module.
src/app/can-load.guard.ts
import { inject } from '@angular/core';
import { CanLoadFn, Route, UrlSegment, Router } from '@angular/router';
import { AuthService } from './auth.service';
export const canLoadGuard: CanLoadFn = (
route: Route,
segments: UrlSegment[]
) => {
const auth = inject(AuthService);
const router = inject(Router);
return auth.isLoggedIn() || router.createUrlTree(['/user']);
};
Step 4: Apply Guards to the Routes
Update main.routes.ts
to protect the admin
route using both CanLoad
and CanActivate
.
src/app/app.routes.ts
import { Routes } from '@angular/router';
import { authGuard } from './auth.guard';
import { canLoadGuard } from './can-load.guard';
export const routes: Routes = [
{
path: 'admin',
canLoad: [canLoadGuard],
canActivate: [authGuard],
loadChildren: () => import('./admin.routes'),
},
{
path: 'user',
loadChildren: () => import('./user.routes'),
},
{
path: '',
redirectTo: 'user',
pathMatch: 'full',
},
];
Step 5: Add Simple Login Buttons (Optional for Testing)
In user/dashboard.component.ts
, add login/logout buttons to test the guard:
import { Component } from '@angular/core';
import { AuthService } from '../auth.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-dashboard',
imports: [],
templateUrl: './dashboard.html',
styleUrl: './dashboard.css'
})
export class Dashboard {
constructor(private auth: AuthService, private router: Router) { }
login() {
this.auth.login();
this.router.navigate(['/admin']);
}
logout() {
this.auth.logout();
this.router.navigate(['/user']);
}
}
With this setup:
-
Users must be “logged in” to access the
admin
route. -
If not, they’re redirected to
/user
. -
The
CanLoad
guard prevents even loading the admin module.
Best Practices for Angular Lazy Loading and Route Guards
Combining lazy loading with route guards provides both performance optimization and access control. But to use them effectively and securely, consider these best practices:
1. Use CanLoad
for Secure Lazy Loading
-
CanLoad
ensures a module isn’t even downloaded if access is denied. -
This is important for protecting sensitive routes like admin dashboards or internal tools.
{
path: 'admin',
loadChildren: () => import('./admin.routes'),
canLoad: [canLoadGuard],
}
⚠️ Without
CanLoad
, a user could potentially download the code even if they're redirected later.
2. Use CanActivate
for In-App Navigation
-
CanActivate
checks access after the module is loaded. -
Ideal for blocking access within already-loaded routes (e.g., when navigating from one internal route to another).
{
path: 'admin',
canActivate: [authGuard],
...
}
Use both
CanLoad
andCanActivate
together for full protection.
3. Keep Routes Modular and Focused
Structure your app into clear, self-contained feature routes:
-
Example:
/admin
,/user
,/settings
-
Keep feature-specific components in their folders.
This enables better scalability and an easier lazy loading setup.
4. Use Route Data for Role-Based Access
You can pass metadata using the data
property to differentiate user roles.
{
path: 'admin',
loadChildren: () => import('./admin.routes'),
canActivate: [roleGuard],
data: { roles: ['admin'] },
}
Then access it inside your guard:
export const roleGuard: CanActivateFn = (route) => {
const auth = inject(AuthService);
const roles = route.data?.['roles'];
return roles?.includes(auth.getUserRole()) || false;
};
5. Use Preloading Strategically
To improve UX while still using lazy loading, you can enable selective preloading:
RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules,
});
This preloads lazy modules after initial load in the background.
6. Avoid Guard Logic in Components
Never place guard logic in components (e.g., redirecting inside ngOnInit
). Use route guards instead — it centralizes your logic and keeps the component clean.
7. Redirect Unauthorized Access
Always return a UrlTree
in your guards to redirect users cleanly.
return router.createUrlTree(['/login']);
This avoids flickering and improper loading behavior.
Following these best practices helps you build fast, secure, and maintainable Angular applications, especially as your project scales.
Real-World Example: Lazy Loading with Auth and Role-Based Guards
In this section, you’ll build a complete flow using:
✅ Lazy-loaded admin
and user
routes
✅ AuthGuard
and CanLoadGuard
to protect the admin
route
✅ RoleGuard
to restrict access based on user roles
✅ Simulated login and logout to test the guards
Step 1: Update the AuthService with Role Logic
Let’s add roles (user
and admin
) to the service.
src/app/auth.service.ts
import { Injectable, signal, computed } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class AuthService {
private _isLoggedIn = signal(false);
private _role = signal<'user' | 'admin'>('user');
readonly isLoggedIn = computed(() => this._isLoggedIn());
readonly role = computed(() => this._role());
loginAs(role: 'user' | 'admin') {
this._isLoggedIn.set(true);
this._role.set(role);
}
logout() {
this._isLoggedIn.set(false);
this._role.set('user');
}
getUserRole(): 'user' | 'admin' {
return this._role();
}
}
Step 2: Create a Role-Based Guard
This guard restricts access based on the route’s data.roles
array.
src/app/role.guard.ts
import { inject } from '@angular/core';
import { CanActivateFn, ActivatedRouteSnapshot, Router } from '@angular/router';
import { AuthService } from './auth.service';
export const roleGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => {
const auth = inject(AuthService);
const router = inject(Router);
const allowedRoles: string[] = route.data?.['roles'] || [];
if (auth.isLoggedIn() && allowedRoles.includes(auth.getUserRole())) {
return true;
}
return router.createUrlTree(['/user']);
};
Step 3: Protect the Admin Route
Update main.routes.ts
to include both canLoad
and canActivate
with role-based protection.
src/app/app.routes.ts
import { Routes } from '@angular/router';
import { canLoadGuard } from './can-load.guard';
import { roleGuard } from './role.guard';
export const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./admin.routes'),
canLoad: [canLoadGuard],
canActivate: [roleGuard],
data: { roles: ['admin'] },
},
{
path: 'user',
loadChildren: () => import('./user.routes'),
},
{
path: '',
redirectTo: 'user',
pathMatch: 'full',
},
];
Step 4: Update the User Dashboard to Simulate Roles
Let users log in as either user
or admin
.
user/dashboard.ts
import { Component } from '@angular/core';
import { AuthService } from '../auth.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-dashboard',
imports: [],
templateUrl: './dashboard.html',
styleUrl: './dashboard.css'
})
export class Dashboard {
constructor(private auth: AuthService, private router: Router) { }
loginAsUser() {
this.auth.loginAs('user');
this.router.navigate(['/user']);
}
loginAsAdmin() {
this.auth.loginAs('admin');
this.router.navigate(['/admin']);
}
logout() {
this.auth.logout();
this.router.navigate(['/user']);
}
}
user/dashboard.html
<h2>User Dashboard</h2>
<p>Log in as:</p>
<button (click)="loginAsUser()">User</button>
<button (click)="loginAsAdmin()">Admin</button>
<button (click)="logout()">Logout</button>
Step 5: Test the Flow
-
Try accessing
/admin
without logging in → redirected to/user
-
Log in as
user
→/admin
still blocked -
Log in as
admin
→ access granted to/admin
-
Logout → all routes reset to public
✅ You now have a fully working example of Angular 20’s standalone routing, lazy loading, auth/role-based guards, and secure navigation.
Conclusion
In this tutorial, you learned how to use Angular 20’s standalone API to implement lazy loading and route guards, creating a scalable and secure routing structure.
We covered:
-
Setting up a standalone Angular app with lazy-loaded routes
-
Creating and applying
CanLoad
,CanActivate
, and role-based guards -
Using
AuthService
to simulate authentication and user roles -
Best practices for route security, performance, and maintainability
By combining lazy loading with route guards, you ensure that:
-
Only the necessary code is loaded when needed
-
Unauthorized users cannot access or even load restricted modules
-
Your app remains fast, modular, and secure as it grows
These techniques are essential for building enterprise-grade Angular applications that are both user-friendly and well-architected.
You can get the full source code on our GitHub.
If you don’t want to waste your time designing your front-end or your budget to spend by hiring a web designer, then Angular Templates is the best place to go. So, speed up your front-end web development with premium Angular templates. Choose your template for your front-end project here.
That's just the basics. If you need more deep learning about Angular, you can take the following cheap course:
- Angular Crash Course - Learn Angular And Google Firebase
- Real-time Communication using Socket. IO 3.x and Angular 11.x
- Angular Progressive Web Apps (PWA) MasterClass & FREE E-Book
- Enterprise-Scale Web Apps with Angular
- Angular Forms In Depth (Angular 20)
- Microservicios Spring Boot y Angular MySql Postgres
-
Angular Developer Interview Questions Practice Test Quiz
Thanks!