Angular Route Guards
What are Route Guards?
Route Guards are interfaces that tell the Angular Router whether to allow or deny navigation to a route. They are used to protect routes from unauthorized access, prevent users from leaving unsaved forms, or pre-fetch data before a route activates. In Angular 21, guards are implemented as simple functions (functional guards) rather than classes.
| Guard | Purpose |
|---|---|
canActivate | Decides if a route can be activated (navigated to) |
canActivateChild | Decides if child routes can be activated |
canDeactivate | Decides if a route can be left (e.g. unsaved changes) |
canMatch | Decides if a route definition should be matched at all |
resolve | Pre-fetches data before the route activates |
canActivate — Protecting Routes
The most common guard. Use it to check if a user is authenticated before allowing access to a protected route. In Angular 21, guards are plain functions that return boolean, UrlTree, or a Promise/Observable of either.
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';
// Functional guard — the modern Angular 21 approach
export const authGuard: CanActivateFn = (route, state) => {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.isLoggedIn()) {
return true;
}
// Redirect to login, preserving the intended URL
return router.createUrlTree(['/login'], {
queryParams: { returnUrl: state.url }
});
};
// auth.service.ts (simplified)
// @Injectable({ providedIn: 'root' })
// export class AuthService {
// private loggedIn = signal(false);
// isLoggedIn() { return this.loggedIn(); }
// login() { this.loggedIn.set(true); }
// logout() { this.loggedIn.set(false); }
// }
import { Routes } from '@angular/router';
import { authGuard } from './auth.guard';
import { DashboardComponent } from './dashboard/dashboard.component';
import { LoginComponent } from './login/login.component';
export const routes: Routes = [
{ path: 'login', component: LoginComponent },
{
path: 'dashboard',
component: DashboardComponent,
canActivate: [authGuard] // protect this route
},
{
path: 'admin',
loadChildren: () => import('./admin/admin.routes').then(m => m.adminRoutes),
canActivate: [authGuard] // protect entire lazy-loaded section
}
];
canDeactivate — Preventing Unsaved Changes
Use canDeactivate to warn users before they navigate away from a form with unsaved changes. The guard can check a component property or call a method on the component.
import { CanDeactivateFn } from '@angular/router';
// Define an interface for components that can be guarded
export interface CanComponentDeactivate {
canDeactivate: () => boolean;
}
export const unsavedChangesGuard: CanDeactivateFn<CanComponentDeactivate> = (component) => {
if (component.canDeactivate()) {
return true;
}
return confirm('You have unsaved changes. Leave anyway?');
};
import { Component, signal } from '@angular/core';
import { CanComponentDeactivate } from './unsaved-changes.guard';
@Component({
selector: 'app-edit-form',
standalone: true,
template: `
<input [(ngModel)]="name" (ngModelChange)="isDirty.set(true)" />
<button (click)="save()">Save</button>
<p *ngIf="isDirty()">You have unsaved changes</p>
`
})
export class EditFormComponent implements CanComponentDeactivate {
name = '';
isDirty = signal(false);
save() {
// ... save logic
this.isDirty.set(false);
}
canDeactivate(): boolean {
return !this.isDirty();
}
}
resolve — Pre-fetching Data
A resolve guard pre-fetches data before the route activates. The component receives the resolved data via ActivatedRoute. This prevents the component from rendering with empty data.
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { HttpClient } from '@angular/common/http';
export interface User { id: number; name: string; email: string; }
export const userResolver: ResolveFn<User> = (route) => {
const http = inject(HttpClient);
const id = route.paramMap.get('id');
return http.get<User>(`https://api.example.com/users/${id}`);
};
import { Routes } from '@angular/router';
import { userResolver } from './user.resolver';
import { UserDetailComponent } from './user-detail.component';
export const routes: Routes = [
{
path: 'users/:id',
component: UserDetailComponent,
resolve: { user: userResolver }
}
];
// In UserDetailComponent:
// constructor(private route: ActivatedRoute) {}
// ngOnInit() {
// const user = this.route.snapshot.data['user'];
// }
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.