Angular Guards and Route Protection: Complete Guide
Angular Route Guards control navigation to and from routes. Learn how to implement CanActivate, CanDeactivate, CanLoad, Resolve guards, and custom guards for authentication, authorization, and route protection in enterprise Angular applications.
Building a secure Angular application means more than just hiding UI elements. I've seen applications where routes were "protected" by hiding navigation links, but users could still access protected routes by typing URLs directly. That's not security—that's security theater. Route Guards are Angular's way of actually protecting routes at the framework level.
Route Guards are interfaces that control navigation to and from routes. They can check authentication status, verify user permissions, prevent navigation when there are unsaved changes, and even prefetch data before a route activates. They're essential for building secure, user-friendly Angular applications.
In this guide, I'll show you how I implement route protection in production Angular applications. We'll cover CanActivate guards (checking if users can access routes), CanDeactivate guards (preventing navigation with unsaved changes), CanLoad guards (preventing lazy-loaded modules from loading), Resolve guards (prefetching data), and combining multiple guards. I'll also share patterns I've learned for handling authentication, authorization, and edge cases like token expiration and permission changes.
CanActivate Guard
Protect routes with authentication:
import { Injectable } from '@angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AuthService } from '../auth/auth.service';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthService,
private router: Router
) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> | Promise<boolean> | boolean {
return this.authService.isAuthenticated.pipe(
map(isAuthenticated => {
if (!isAuthenticated) {
localStorage.setItem('returnUrl', state.url);
this.authService.Logout();
this.router.navigate(['/login']);
return false;
}
return true;
})
);
}
}
// Use in routes
const routes: Routes = [
{
path: 'business',
canActivate: [AuthGuard],
loadChildren: () => import('./business/business.module').then(m => m.BusinessModule)
}
];CanDeactivate Guard
Prevent navigation with unsaved changes:
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { Observable } from 'rxjs';
export interface CanComponentDeactivate {
canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}
@Injectable({
providedIn: 'root'
})
export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
canDeactivate(
component: CanComponentDeactivate
): Observable<boolean> | Promise<boolean> | boolean {
return component.canDeactivate ? component.canDeactivate() : true;
}
}
// Component implementation
export class BusinessFormComponent implements CanComponentDeactivate {
form: FormGroup;
isDirty: boolean = false;
canDeactivate(): boolean {
if (this.form.dirty && !this.isDirty) {
return confirm('You have unsaved changes. Do you want to leave?');
}
return true;
}
}
// Use in routes
{
path: 'business/:id/edit',
component: BusinessFormComponent,
canDeactivate: [CanDeactivateGuard]
}CanLoad Guard
Prevent lazy module loading:
import { Injectable } from '@angular/core';
import { CanLoad, Route } from '@angular/router';
import { AuthService } from '../auth/auth.service';
@Injectable({
providedIn: 'root'
})
export class CanLoadGuard implements CanLoad {
constructor(private authService: AuthService) {}
canLoad(route: Route): boolean {
return this.authService.hasPermission(route.data?.['permission']);
}
}
// Use in routes
{
path: 'admin',
canLoad: [CanLoadGuard],
data: { permission: 'admin' },
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
}Resolve Guard
Prefetch data before route activation:
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { BusinessService } from '../services/business.service';
@Injectable({
providedIn: 'root'
})
export class BusinessResolver implements Resolve<any> {
constructor(private businessService: BusinessService) {}
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<any> | Promise<any> | any {
const businessId = +route.params['id'];
return this.businessService.GetBusiness(businessId);
}
}
// Use in routes
{
path: 'business/:id',
component: BusinessDetailsComponent,
resolve: { business: BusinessResolver }
}
// Access in component
export class BusinessDetailsComponent implements OnInit {
constructor(private route: ActivatedRoute) {}
ngOnInit(): void {
this.route.data.subscribe(data => {
this.business = data['business'];
});
}
}Multiple Guards
Combine multiple guards:
const routes: Routes = [
{
path: 'business',
canActivate: [AuthGuard, CheckUserGuard],
data: { layout: 'show', userType: loginUserType.Admin },
loadChildren: () => import('./business/business.module').then(m => m.BusinessModule)
}
];Best Practices
- Use CanActivate for authentication and authorization checks
- Use CanDeactivate to prevent data loss from unsaved changes
- Use CanLoad to prevent loading unauthorized feature modules
- Use Resolve to prefetch data before route activation
- Store return URLs for redirecting after authentication
- Handle guard failures gracefully with user-friendly messages
- Combine multiple guards when needed
- Use route data to pass configuration to guards
- Test guards independently from components
- Document guard behavior and requirements
Conclusion
Angular Route Guards provide essential security and user experience features. By implementing appropriate guards, you can protect routes, prevent unauthorized access, handle unsaved changes, and improve application performance with data prefetching.