Functional Route Guards
Functional guards are the modern approach. They are simple functions that decide whether navigation proceeds.
canActivate — Protect a Route
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';
export const authGuard: CanActivateFn = (route, state) => {
const authService = inject(AuthService);
const router = inject(Router);
if (authService.isLoggedIn) {
return true;
}
// Redirect to login with return URL
return router.createUrlTree(['/login'], {
queryParams: { returnUrl: state.url },
});
};
export const routes: Routes = [
{ path: 'dashboard', component: DashboardComponent, canActivate: [authGuard] },
];
canMatch — Conditionally Match a Route
export const adminGuard: CanMatchFn = (route, segments) => {
const authService = inject(AuthService);
return authService.isAdmin;
};
export const routes: Routes = [
{
path: 'admin',
canMatch: [adminGuard],
loadChildren: () => import('./admin/admin.routes'),
},
];
canDeactivate — Prevent Leaving a Route
export interface CanComponentDeactivate {
canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}
export const unsavedChangesGuard: CanDeactivateFn<CanComponentDeactivate> = (
component: CanComponentDeactivate
) => {
return component.canDeactivate ? component.canDeactivate() : true;
};
Guard Return Types
| Return | Effect |
|---|---|
true |
Allow navigation |
false |
Cancel navigation |
UrlTree |
Redirect to another route |
Observable<boolean | UrlTree> |
Async decision |
Promise<boolean | UrlTree> |
Async decision |
Resolvers — Prefetching Data
Resolvers fetch data before a route activates, ensuring the component renders with content already available.
Creating a Resolver
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { UserService } from './user.service';
export const userResolver: ResolveFn<User> = (route, state) => {
const userService = inject(UserService);
const id = route.paramMap.get('id')!;
return userService.getUser(Number(id));
};
export const routes: Routes = [
{
path: 'users/:id',
component: UserDetailComponent,
resolve: { user: userResolver },
},
];
Accessing Resolved Data
@Component({
selector: 'app-user-detail',
template: `<h1>{{ user.name }}</h1>`,
})
export class UserDetailComponent {
private route = inject(ActivatedRoute);
user!: User;
constructor() {
this.user = this.route.snapshot.data['user'];
}
}
Resolver with Loading State
export const userResolver: ResolveFn<User> = (route, state) => {
const userService = inject(UserService);
const id = route.paramMap.get('id')!;
return userService.getUser(Number(id)).pipe(
catchError(() => {
// Handle error — redirect or return default
return of(null);
})
);
};
Guard + Resolver Combination
export const routes: Routes = [
{
path: 'users/:id',
canActivate: [authGuard],
resolve: { user: userResolver },
component: UserDetailComponent,
},
];
The guard runs first. If it passes, the resolver fetches data. Then the component renders with the resolved data.
Quiz
1. What is the modern functional guard syntax?
2. How do you redirect in a guard?
3. What does a resolver do?
Flashcards
Question
What is a functional guard?
Click to reveal answer
Answer
CanActivateFn — a function using inject() that returns true, false, or UrlTree.
Question
How do you redirect in a guard?
Click to reveal answer
Answer
return router.createUrlTree(['/login']) — returning a UrlTree redirects.
Question
What does a resolver do?
Click to reveal answer
Answer
Fetches data before route activation. Available via route.snapshot.data.
Revision Notes
Key Takeaways
- 1. Functional guards (CanActivateFn) are the modern approach
- 2. Return UrlTree to redirect from a guard
- 3. Resolvers prefetch data before route activation
- 4. Guards run before resolvers
Interview Tips
- • Explain functional vs class-based guards
- • Be ready to describe the guard return types
- • Mention resolvers for data prefetching
Cheat Sheet
Guards and Resolvers Cheat Sheet
Functional guard:
export const authGuard: CanActivateFn = (route, state) => {
return inject(AuthService).isLoggedIn
? true
: inject(Router).createUrlTree(['/login']);
};
Resolver:
export const userResolver: ResolveFn<User> = (route) => {
return inject(UserService).getUser(route.paramMap.get('id')!);
};
Route config:
{ path: 'users/:id', canActivate: [authGuard], resolve: { user: userResolver } }