1. Introduction to Route Guards
In modern Angular applications, routing is not just about navigating between views. It’s also about controlling access to certain areas of your application based on conditions like user authentication, role-based access, or unsaved changes.
Angular provides route guards as a built-in mechanism to control navigation. They allow developers to determine whether a route can be activated, deactivated, loaded, or accessed by child routes.
Among the various types of guards (CanActivate
, CanDeactivate
, CanLoad
, CanActivateChild
, and Resolve
), CanActivate is the most commonly used for restricting access to routes.
2. Understanding CanActivate Guard
The CanActivate
interface defines a guard that determines if a route can be activated. When a user tries to navigate to a route, Angular calls the canActivate
method of the guard. If it returns:
true
→ navigation proceedsfalse
→ navigation is canceled
This guard is primarily used to:
- Restrict access to routes based on authentication
- Check for user roles or permissions
- Prevent unauthorized users from accessing sensitive areas
3. Setting Up the Auth Service
Before implementing the guard, you need an authentication service to check whether a user is logged in.
Example AuthService
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class AuthService {
private loggedIn = false;
login() {
this.loggedIn = true;
}
logout() {
this.loggedIn = false;
}
isLoggedIn(): boolean {
return this.loggedIn;
}
}
Here:
login()
andlogout()
methods simulate authentication.isLoggedIn()
returns a boolean indicating the user’s login status.
4. Creating the CanActivate Guard
Use Angular CLI or manual creation to generate a guard:
ng generate guard auth
This creates a guard implementing the CanActivate
interface.
AuthGuard Example
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 authService: AuthService, private router: Router) {}
canActivate(): boolean {
if (this.authService.isLoggedIn()) {
return true;
} else {
this.router.navigate(['/login']);
return false;
}
}
}
Explanation
- The guard injects
AuthService
to check login status. - It injects
Router
to redirect unauthorized users. - Returns
true
if the user is logged in, otherwise navigates to/login
and returnsfalse
.
5. Applying the CanActivate Guard to Routes
Once the guard is created, you need to attach it to the route(s) you want to protect.
Example Route Setup
import { Routes } from '@angular/router';
import { DashboardComponent } from './dashboard/dashboard.component';
import { AuthGuard } from './auth.guard';
import { LoginComponent } from './login/login.component';
const routes: Routes = [
{ path: '', component: LoginComponent },
{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] }
];
Here:
- The
DashboardComponent
is protected. - Users who are not logged in are redirected to the login page.
6. Handling Asynchronous Authentication
In real applications, authentication checks are often asynchronous, e.g., verifying a token with a backend server. In such cases, canActivate
can return:
Observable<boolean>
Promise<boolean>
Example with Observable
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from './auth.service';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(): Observable<boolean> {
return this.authService.checkToken().pipe(
map(isValid => {
if (isValid) {
return true;
} else {
this.router.navigate(['/login']);
return false;
}
})
);
}
}
AuthService Example for Token Check
checkToken(): Observable<boolean> {
// Simulate HTTP request to validate token
return of(localStorage.getItem('token') ? true : false);
}
This approach ensures guards work even when validation requires asynchronous operations.
7. Using CanActivate with Multiple Guards
You can apply multiple guards to a single route. Angular evaluates them in the order specified. All guards must return true
for navigation to proceed.
Example
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard, RoleGuard]
}
Here:
AuthGuard
checks if the user is logged in.RoleGuard
checks if the user has the necessary role (like ‘admin’).
8. Role-Based Route Protection
Guards can enforce role-based access control (RBAC) by checking user roles.
RoleGuard Example
@Injectable({ providedIn: 'root' })
export class RoleGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(): boolean {
const userRole = this.authService.getUserRole(); // e.g., 'admin'
if (userRole === 'admin') {
return true;
} else {
this.router.navigate(['/unauthorized']);
return false;
}
}
}
Usage in Routes:
{ path: 'admin', component: AdminComponent, canActivate: [AuthGuard, RoleGuard] }
9. Redirecting Unauthorized Users
Instead of simply preventing access, it’s a good practice to redirect unauthorized users to a meaningful page, such as:
- Login page (
/login
) - Unauthorized access page (
/unauthorized
)
This improves user experience and provides clear navigation feedback.
10. CanActivate Child Routes
CanActivateChild
is useful when you want to guard all child routes of a parent module.
Example
{
path: 'admin',
component: AdminComponent,
canActivateChild: [AuthGuard],
children: [
{ path: 'users', component: UsersComponent },
{ path: 'settings', component: SettingsComponent }
]
}
Here, the guard runs whenever a user navigates to /admin/users
or /admin/settings
.
11. Best Practices for CanActivate Guards
- Keep guards simple: Only include logic related to navigation.
- Return Observable or Promise when dealing with async checks.
- Avoid side effects in guards; use services for data operations.
- Combine with other guards for layered security (e.g., Auth + Role).
- Use redirect routes to improve UX for unauthorized access.
- Centralize authentication logic in a service to avoid duplication.
- Test guards to ensure they prevent unauthorized access correctly.
12. Testing CanActivate Guards
Testing guards ensures your route protection works as expected.
Example Unit Test
import { TestBed } from '@angular/core/testing';
import { Router } from '@angular/router';
import { AuthGuard } from './auth.guard';
import { AuthService } from './auth.service';
describe('AuthGuard', () => {
let guard: AuthGuard;
let authService: AuthService;
let routerSpy = { navigate: jasmine.createSpy('navigate') };
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
AuthGuard,
{ provide: AuthService, useValue: { isLoggedIn: () => false } },
{ provide: Router, useValue: routerSpy }
]
});
guard = TestBed.inject(AuthGuard);
authService = TestBed.inject(AuthService);
});
it('should redirect unauthorized users', () => {
expect(guard.canActivate()).toBe(false);
expect(routerSpy.navigate).toHaveBeenCalledWith(['/login']);
});
});
13. Using CanActivate with Lazy-Loaded Modules
Guards also protect lazy-loaded modules using the canLoad
interface. However, CanActivate
still works for routes within the lazy-loaded module.
Example
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
canActivate: [AuthGuard]
}
Navigation to /admin
will trigger the AuthGuard
.
14. Handling Observables in Guards
For guards that rely on backend checks, you can use HTTP requests combined with RxJS operators.
Example
canActivate(): Observable<boolean> {
return this.authService.validateToken().pipe(
map(response => response.isValid),
catchError(() => {
this.router.navigate(['/login']);
return of(false);
})
);
}
This allows you to handle asynchronous authentication gracefully.
15. Summary
- CanActivate guards control whether a route can be accessed.
- Useful for authentication, role-based access, and secure navigation.
- Guards can return boolean, Observable<boolean>, or Promise<boolean>.
- Combine multiple guards for layered security.
- Use
Router
to redirect unauthorized users. - Guards can work with lazy-loaded modules and child routes.
- Keep guard logic simple, testable, and centralized in services.
Proper implementation of CanActivate
guards ensures that your Angular application is secure, user-friendly, and maintainable.
16. Complete Example
AuthService
@Injectable({ providedIn: 'root' })
export class AuthService {
private loggedIn = false;
login() { this.loggedIn = true; }
logout() { this.loggedIn = false; }
isLoggedIn(): boolean { return this.loggedIn; }
}
AuthGuard
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(private auth: AuthService, private router: Router) {}
canActivate(): boolean {
if (this.auth.isLoggedIn()) return true;
this.router.navigate(['/login']);
return false;
}
}
Routes
const routes: Routes = [
{ path: '', component: LoginComponent },
{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] }
];
This configuration provides basic authentication-based route protection in Angular.
Leave a Reply