Building secure and reliable web applications often requires controlling which users can access specific routes or components.
In Angular, this is achieved using Route Guards — special classes that decide whether navigation to a route should be allowed or prevented.
This comprehensive post explores every aspect of Angular Route Guards — their purpose, types, implementation, and best practices.
We will also walk through real examples of CanActivate, CanDeactivate, CanLoad, Resolve, and CanActivateChild guards.
1. Introduction to Route Guards
In Angular, route guards are used to control navigation between routes.
They help in situations such as:
- Restricting access to authenticated users.
- Preventing users from leaving unsaved forms.
- Delaying route loading until data is fetched.
- Blocking feature modules from unauthorized access.
Each guard implements a specific Angular interface, and each interface serves a distinct purpose.
2. Why Use Route Guards?
Without guards, any user could navigate to any route by simply entering a URL manually.
Guards provide a security layer that allows you to:
- Protect sensitive routes (like dashboard or admin panels).
- Prevent accidental navigation (when form data is unsaved).
- Load necessary data before route activation.
- Restrict lazy-loaded modules until conditions are met.
3. Types of Angular Route Guards
Angular provides five main types of guards:
Guard Type | Purpose |
---|---|
CanActivate | Decides if a route can be activated |
CanDeactivate | Checks if the user can leave a route |
CanLoad | Determines if a lazy-loaded module can be loaded |
Resolve | Pre-fetches data before activating a route |
CanActivateChild | Protects child routes of a given route |
Let’s explore each of these in detail.
4. Setting Up Route Guards
Before creating guards, make sure your Angular app has routing configured.
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { DashboardComponent } from './dashboard.component';
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'dashboard', component: DashboardComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
Once routing is configured, you can begin creating guards.
5. CanActivate Guard
Purpose
CanActivate
determines if a route can be activated.
It’s commonly used for authentication — allowing only logged-in users to access specific routes.
Example: Authentication Guard
Step 1: Create AuthService
@Injectable({ providedIn: 'root' })
export class AuthService {
isLoggedIn = false;
login() {
this.isLoggedIn = true;
}
logout() {
this.isLoggedIn = false;
}
}
Step 2: Create AuthGuard
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(['/']);
return false;
}
}
}
Step 3: Apply Guard to Routes
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] }
];
Now, users cannot access /dashboard
unless logged in.
6. CanDeactivate Guard
Purpose
CanDeactivate
checks if a user can leave a route.
It is often used to prevent users from losing unsaved data when navigating away.
Example: Unsaved Form Guard
Step 1: Create a Component with Form
@Component({
selector: 'app-edit-profile',
template: `
<form>
<input type="text" [(ngModel)]="name" />
<button>Save</button>
</form>
`
})
export class EditProfileComponent {
name = '';
saved = false;
canDeactivate(): boolean {
return this.saved || confirm('You have unsaved changes. Leave anyway?');
}
}
Step 2: Create CanDeactivate Guard
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
export interface CanComponentDeactivate {
canDeactivate: () => boolean;
}
@Injectable({ providedIn: 'root' })
export class DeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
canDeactivate(component: CanComponentDeactivate): boolean {
return component.canDeactivate ? component.canDeactivate() : true;
}
}
Step 3: Apply to Route
const routes: Routes = [
{ path: 'edit', component: EditProfileComponent, canDeactivate: [DeactivateGuard] }
];
Now, if the user tries to navigate away without saving, a confirmation appears.
7. CanLoad Guard
Purpose
CanLoad
prevents a lazy-loaded module from being loaded if the user doesn’t meet conditions, such as authentication.
This guard runs before the module is even downloaded, improving performance and security.
Example: Restrict Lazy Loading
Step 1: Define a Lazy Module
const routes: Routes = [
{ path: '', component: DashboardComponent }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class DashboardRoutingModule {}
Step 2: Protect It in App Routing
const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule),
canLoad: [AuthGuard]
}
];
Step 3: Update AuthGuard
import { CanLoad, Route, UrlSegment } from '@angular/router';
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanLoad {
constructor(private auth: AuthService, private router: Router) {}
canLoad(route: Route, segments: UrlSegment[]): boolean {
if (this.auth.isLoggedIn) {
return true;
} else {
this.router.navigate(['/']);
return false;
}
}
}
This ensures the dashboard module is loaded only for authenticated users.
8. Resolve Guard
Purpose
Resolve
guard is used to pre-fetch data before a route is activated.
This ensures that required data is available as soon as the component loads.
Example: Data Resolver
Step 1: Create a Resolver
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
import { Observable, of } from 'rxjs';
import { UserService } from './user.service';
@Injectable({ providedIn: 'root' })
export class UserResolver implements Resolve<any> {
constructor(private userService: UserService) {}
resolve(): Observable<any> {
return this.userService.getUser();
}
}
Step 2: Apply to Route
const routes: Routes = [
{
path: 'profile',
component: ProfileComponent,
resolve: { user: UserResolver }
}
];
Step 3: Access Resolved Data in Component
@Component({
selector: 'app-profile',
template: <h2>{{ user?.name }}</h2>
})
export class ProfileComponent {
user: any;
constructor(private route: ActivatedRoute) {
this.user = this.route.snapshot.data['user'];
}
}
This ensures the component renders only after the user data has been fetched.
9. CanActivateChild Guard
Purpose
CanActivateChild
is used to protect child routes of a parent route.
It ensures that child routes are accessible only if certain conditions are met.
Example: Protecting Child Routes
Step 1: Create AdminGuard
@Injectable({ providedIn: 'root' })
export class AdminGuard implements CanActivateChild {
constructor(private auth: AuthService, private router: Router) {}
canActivateChild(): boolean {
if (this.auth.isLoggedIn) {
return true;
} else {
this.router.navigate(['/']);
return false;
}
}
}
Step 2: Apply to Route
const routes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivateChild: [AdminGuard],
children: [
{ path: 'users', component: UsersComponent },
{ path: 'settings', component: SettingsComponent }
]
}
];
Now, all child routes of /admin
are protected.
10. Combining Multiple Guards
You can use multiple guards on the same route.
const routes: Routes = [
{
path: 'dashboard',
component: DashboardComponent,
canActivate: [AuthGuard],
canDeactivate: [DeactivateGuard]
}
];
Here, both authentication and unsaved-changes checks will run before navigation.
11. Returning Observables or Promises
Guards can return:
- boolean
- Observable<boolean>
- Promise<boolean>
Example returning Observable:
canActivate(): Observable<boolean> {
return this.authService.checkLoginStatus();
}
This allows asynchronous checks like API validation or token verification.
12. Guard Execution Order
Angular executes guards in this order:
CanDeactivate
CanLoad
CanActivateChild
CanActivate
Resolve
If any guard returns false
, navigation is canceled.
13. Redirecting Within Guards
You can programmatically redirect inside a guard using the Angular Router.
canActivate(): boolean {
if (!this.auth.isLoggedIn) {
this.router.navigate(['/login']);
return false;
}
return true;
}
This ensures users are redirected instead of simply being blocked.
14. Handling Async Guards with RxJS
Example using RxJS operators:
canActivate(): Observable<boolean> {
return this.authService.user$.pipe(
map(user => !!user),
tap(isLoggedIn => {
if (!isLoggedIn) this.router.navigate(['/login']);
})
);
}
This approach integrates seamlessly with reactive authentication systems.
15. Common Use Cases
Use Case | Guard Type |
---|---|
Restrict route to authenticated users | CanActivate |
Confirm before leaving form | CanDeactivate |
Block unauthorized module loading | CanLoad |
Fetch data before route loads | Resolve |
Protect child routes under a parent | CanActivateChild |
16. Testing Route Guards
Guards can be unit tested like any Angular service.
Example test:
it('should block navigation when not logged in', () => {
const authService = { isLoggedIn: false } as AuthService;
const guard = new AuthGuard(authService as any, router as any);
expect(guard.canActivate()).toBeFalse();
});
Testing guards ensures predictable navigation behavior across environments.
17. Best Practices for Route Guards
- Keep guards lightweight.
Do not overload them with complex logic. - Avoid API calls inside guards when possible.
UseResolve
for data loading. - Reuse logic through services.
Authentication or permission checks should live in separate services. - Return Observables for asynchronous checks.
- Use
CanLoad
for lazy-loaded modules. - Keep guards pure and side-effect-free except for navigation redirection.
- Combine guards logically.
Example:AuthGuard
+RoleGuard
for admin routes. - Handle navigation errors gracefully.
- Always test your guards.
18. Example: Full Implementation
Here’s an example combining multiple guards in a real-world scenario.
const routes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard],
canActivateChild: [RoleGuard],
canLoad: [AuthGuard],
children: [
{
path: 'users',
component: UsersComponent,
resolve: { users: UsersResolver },
canDeactivate: [DeactivateGuard]
}
]
}
];
This configuration ensures:
- Only authenticated users can load and access admin routes.
- Child routes require special roles.
- User data is preloaded.
- Leaving without saving changes prompts a confirmation.
19. Handling Roles and Permissions
Guards can also handle role-based access control.
@Injectable({ providedIn: 'root' })
export class RoleGuard implements CanActivate {
constructor(private auth: AuthService, private router: Router) {}
canActivate(): boolean {
const user = this.auth.getUser();
if (user.role === 'admin') {
return true;
} else {
this.router.navigate(['/unauthorized']);
return false;
}
}
}
Leave a Reply