Angular Routing Guards
โฑ๏ธ ~15-minute bite ยท solve the sandbox to master
5-Year-Old Metaphor
โ The physical, real-world picture. No jargon.Guards = airport security checkpoints at boarding. Each checkpoint has a different job.
- canMatch = airline check โ is this even your flight? If not, you get directed to a different gate entirely.
- canActivate= passport control โ can you enter the country? You're at the right gate but may still be turned away.
- canActivateChild = gate check โ can you board this specific flight within the terminal?
- resolve = the crew briefing before doors open. Data loads before passengers board โ no half-ready component.
- canDeactivate= "are you sure you want to leave the gate area?" โ prevents accidental abandonment of unsaved work.
Interactive Sandbox
โ Move something, see it react instantly.Guard Chain Simulator
Guards navigation to a route. Returns boolean (allow/deny) or UrlTree (redirect). The router has already matched this route โ canActivate decides if the user can enter.
| 1 | import { CanActivateFn, Router, UrlTree } from '@angular/router'; |
| 2 | import { inject } from '@angular/core'; |
| 3 | import { map } from 'rxjs/operators'; |
| 4 | import { AuthService } from './auth.service'; |
| 5 | ย |
| 6 | // Simple boolean guard |
| 7 | export const authGuard: CanActivateFn = (): boolean => { |
| 8 | const isLoggedIn = inject(AuthService).isLoggedIn(); |
| 9 | if (!isLoggedIn) { |
| 10 | inject(Router).navigate(['/login']); |
| 11 | } |
| 12 | return isLoggedIn; |
| 13 | }; |
| 14 | ย |
| 15 | // UrlTree redirect โ preferred over imperative navigate() |
| 16 | export const authGuardWithUrlTree: CanActivateFn = (): boolean | UrlTree => { |
| 17 | const auth = inject(AuthService); |
| 18 | const router = inject(Router); |
| 19 | return auth.isLoggedIn() |
| 20 | ? true |
| 21 | : router.createUrlTree(['/login'], { |
| 22 | queryParams: { returnUrl: router.url }, // preserve intended URL |
| 23 | }); |
| 24 | }; |
| 25 | ย |
| 26 | // Async guard returning Observable |
| 27 | export const roleGuard: CanActivateFn = ( |
| 28 | route |
| 29 | ): Observable<boolean | UrlTree> => { |
| 30 | const requiredRole = route.data['role'] as string; |
| 31 | const router = inject(Router); |
| 32 | return inject(AuthService).currentUser$.pipe( |
| 33 | map(user => |
| 34 | user?.roles.includes(requiredRole) |
| 35 | ? true |
| 36 | : router.createUrlTree(['/forbidden']) |
| 37 | ) |
| 38 | ); |
| 39 | }; |
| 40 | ย |
| 41 | // Route definition |
| 42 | export const routes: Routes = [ |
| 43 | { path: 'admin', component: AdminComponent, canActivate: [authGuardWithUrlTree] }, |
| 44 | { path: 'settings', component: SettingsComponent, canActivate: [authGuard, roleGuard], |
| 45 | data: { role: 'admin' } }, // multiple guards: all must return true |
| 46 | ]; |
- โMultiple guards in the array: Angular runs them in parallel (Angular 15+) โ all must pass
- โUrlTree redirect is preferred over imperative router.navigate() โ cleaner and testable
- โReturning an Observable: it must complete โ use take(1) or first() if it's a long-lived stream
Challenge
Explore all 5 guard types. Use the toggles to simulate guard pass/fail and run the chain.
Why Should I Care?
โ The exact interview question + the bug it kills.Q: What's the difference between canActivate and canMatch?
canMatch runs during route matching โ if it returns false, the router tries the next route definition for the same URL. canActivate runs after matching โ the route is already selected, and false blocks navigation (often redirecting). canMatch = 'does this route exist for you?'. canActivate = 'can you enter this route?'
Q: When should you use resolve vs fetching data in ngOnInit?
resolve prevents partially-loaded component views โ the component renders with data already present, no loading spinner needed inside the component. ngOnInit is simpler and better for non-critical or user-triggered data loads. Use resolve when missing data would leave the view in a broken state.
Q: Why prefer functional guards over class-based guards?
Functional guards (CanActivateFn) use inject() directly โ no DI class, no implements CanActivate boilerplate, no @Injectable(). They're tree-shakeable, easier to unit-test (just call the function), and the Angular team considers them the idiomatic path forward.
The Deep Dive
โ Spec refs, engine internals, the minutiae.Guard internals, return types, and advanced routing patterns:
- 01
inject() inside functional guards: works in the injection context of the guard call โ inject(Router), inject(AuthService), etc. No constructor needed.
- 02
UrlTree redirects: router.createUrlTree(['/login'], { queryParams: { returnUrl: router.url } }) โ the router processes the UrlTree as a navigation, not an imperative side effect. Cleaner and testable.
- 03
Guard return types in full: boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree>. All forms are valid; Observable must complete.
- 04
canMatch for feature flags: define two routes with the same path. canMatch on the first checks the flag. If false, router falls through to the second route. Zero impact on the URL.
- 05
Angular 15+ parallel guard execution: multiple guards in a canActivate array run concurrently โ Angular waits for all before deciding. First falsy result wins (short-circuits).
Interview Questions
โ Real questions from real interviews โ with answers.canMatch runs during route selection; canActivate runs after a route is already matched.
UrlTree is declarative โ Angular processes it as part of navigation, making it testable and avoiding race conditions.
The guard's first argument is the component instance โ use an interface to avoid a tight import dependency.
A resolver pre-fetches data before the component renders โ the component always has data on first render, no loading state needed.
Functional guards use inject() directly โ no class, no @Injectable, no implements boilerplate, tree-shakeable.
Angular 15+ runs them in parallel โ all must return true. The first false short-circuits.
Memory Game
โ Quick quiz โ lock the concept in long-term memory.What is the difference between route children and loadChildren?