Angular Route Guards: Choose, Configure, Test
- Average speed
- km/h
- Result
- Finish time
- 50 min
- You entered
- Distance
- 10 km
- You entered
Angular route guards are interfaces that control whether a user can navigate to, away from, or load a route, and you choose, configure, and test them by implementing the right guard interface, registering it in the route’s canActivate (or related) property, and writing unit tests that mock the guard’s dependencies. This article explains the four built-in guard types, how to select the correct one for your access rules, how to wire them into your routing module, and how to test them reliably.
Background: What Are Route Guards?
Angular’s router processes navigation as a series of steps. Before activating a route, the router checks any guards attached to that route. Guards are plain Angular services that implement one of several interfaces. Each interface defines a method that returns a boolean, an Observable<boolean>, or a Promise<boolean>. If the value resolves to true, navigation continues; if false, navigation is cancelled. Guards can also return a UrlTree to redirect the user to a different route, which is useful for sending unauthenticated users to a login page.
There are four guard interfaces in Angular (as of version 15 and later): CanActivate, CanActivateChild, CanDeactivate, and CanLoad (deprecated in favor of CanMatch in newer versions). Additionally, Resolve is often grouped with guards, but it prefetches data rather than controlling access. Each serves a distinct purpose in the navigation lifecycle.
How Route Guards Work in the Navigation Cycle
When a user clicks a link or calls router.navigate(), the router starts a sequence. It first resolves the target URL into a route tree, then runs guards in a specific order:
CanLoad(orCanMatch) – runs before any lazy-loaded module is fetched. If it returnsfalse, the module is never loaded.CanActivateChild– runs on the parent route before any child route is activated.CanActivate– runs on the route itself before the component is created.Resolve– runs after guards pass but before the component is instantiated, to fetch data.CanDeactivate– runs when navigating away from the current route, allowing you to confirm unsaved changes.
Guards execute in the order they are listed in the route configuration, and all must return true for navigation to proceed. If any guard returns false, navigation is aborted and the URL stays the same (unless a redirect is provided).
For example, a route with canActivate: [AuthGuard, AdminGuard] will run AuthGuard first. If it returns false, AdminGuard is never executed.
How to Choose the Right Guard for Your Access Rules
Selecting the correct guard depends on what you want to protect and when you want the check to happen.
- Use
CanActivatewhen you need to allow or block access to a specific route (e.g., only logged-in users can view the dashboard). This is the most common guard for authentication and authorization checks. - Use
CanActivateChildwhen you want to protect all child routes of a parent. For example, if you have a/adminsection with multiple sub-routes, placing a guard on the parent withcanActivateChildsaves you from repeating the same guard on every child. - Use
CanDeactivatewhen you need to prevent navigation away from a route, typically to warn about unsaved form changes. It receives the current component instance, so you can call a method on it to check if it has unsaved data. - Use
CanMatch(or legacyCanLoad) when you want to prevent a lazy-loaded module from being downloaded at all. This is useful for feature modules that should only be accessible to certain users, as it avoids loading code they can’t use. - Use
Resolvewhen you need to fetch data before the route activates, ensuring the component has data ready. While not strictly an access guard, it often works alongside guards to prepare the page.
For role-based access (e.g., admin vs. regular user), you’ll typically combine an authentication check with an authorization check. A common pattern is to have an AuthGuard that verifies the user is logged in, and an AdminGuard that checks the user’s role. You can attach both to the same route, or use a single guard that does both checks.
How to Configure Guards in Your Routing Module
To use a guard, you first create a service that implements the guard interface. For example, an AuthGuard might look like this:
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(): boolean {
if (this.authService.isLoggedIn()) {
return true;
}
return this.router.createUrlTree(['/login']);
}
}
Then, in your routing module, you attach the guard to a route:
const routes: Routes = [
{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] },
{ path: 'admin', canActivateChild: [AdminGuard], children: [
{ path: 'users', component: UsersComponent },
{ path: 'settings', component: SettingsComponent }
]}
];
Guards are provided through Angular’s dependency injection, so they can inject services like AuthService or Router. You can also pass data to guards via the route’s data property, allowing a single guard to handle multiple roles:
{ path: 'admin', component: AdminComponent, canActivate: [RoleGuard], data: { role: 'admin' } }
In the guard, you can access this data via the ActivatedRouteSnapshot parameter.
Common Problems and Misconceptions
One common mistake is using CanActivate for lazy-loaded modules. Since CanActivate runs after the module is loaded, it doesn’t prevent the module’s code from downloading. If you want to prevent the download, use CanMatch (or CanLoad in older versions) on the route that points to the lazy module.
Another misconception is that guards run on every navigation. In reality, guards only run when the route they are attached to is being activated. If you navigate to the same route with different parameters (e.g., /product/1 to /product/2), the guard will run again because the route is being re-activated. However, if you navigate to a child route, the parent’s CanActivate guard will not re-run unless the parent route itself is re-activated.
Guards that return an Observable must complete, otherwise navigation will hang. If you use an HTTP request, make sure to use first() or take(1) to complete the observable. Similarly, if you return a Promise, it must resolve.
Order matters: guards are executed in the order they appear in the array. If you have a guard that redirects, place it after any guards that might return false without redirecting, otherwise the redirect may be overridden.
How to Test Route Guards
Testing guards is straightforward because they are services. You can instantiate the guard with mocked dependencies and call its method directly.
For example, to test AuthGuard:
describe('AuthGuard', () => {
let guard: AuthGuard;
let authServiceMock: { isLoggedIn: jasmine.Spy };
let routerMock: { createUrlTree: jasmine.Spy };
beforeEach(() => {
authServiceMock = { isLoggedIn: jasmine.createSpy('isLoggedIn') };
routerMock = { createUrlTree: jasmine.createSpy('createUrlTree') };
guard = new AuthGuard(authServiceMock as any, routerMock as any);
});
it('should return true if user is logged in', () => {
authServiceMock.isLoggedIn.and.returnValue(true);
expect(guard.canActivate()).toBeTrue();
});
it('should redirect to login if not logged in', () => {
authServiceMock.isLoggedIn.and.returnValue(false);
routerMock.createUrlTree.and.returnValue('login');
expect(guard.canActivate()).toBe('login');
expect(routerMock.createUrlTree).toHaveBeenCalledWith(['/login']);
});
});
When testing guards that use ActivatedRouteSnapshot or RouterStateSnapshot, you can create simple mock objects with the required properties. For guards that return observables, you can use fakeAsync and tick from @angular/core/testing to simulate asynchronous completion.
It’s also important to test the integration of guards with the router in a RouterTestingModule to ensure the route configuration is correct and that redirects work as expected.
Alternatives to Route Guards
While guards are the standard way to control access, there are alternatives for certain scenarios:
- Route resolvers can be used to fetch data before activation, but they don’t prevent access; they only delay navigation until data is ready.
- Interceptors work at the HTTP level, not the route level. They can block API calls, but they don’t affect what the user sees in the UI.
- Component-level checks in
ngOnInitcan redirect users, but this is less secure because the component is still created, and it doesn’t prevent the route from being activated. - Feature flags can be used with
CanMatchto conditionally enable routes based on configuration, but they are not a replacement for authentication.
For most access control needs, route guards are the recommended approach because they integrate with the navigation lifecycle and are easy to test.
Frequently Asked Questions
What is the difference between CanActivate and CanActivateChild?
CanActivate runs on the route it is attached to, while CanActivateChild runs on the parent route before any child route is activated. If you have a parent route with multiple children, using CanActivateChild on the parent is more efficient than repeating CanActivate on each child.
Can I use multiple guards on the same route?
Yes, you can pass an array of guards to canActivate, canActivateChild, or canDeactivate. They run in order, and if any returns false, navigation is cancelled. This is useful for separating authentication and authorization checks.
How do I redirect a user to a login page if they are not authenticated?
In your guard, return a UrlTree created with this.router.createUrlTree(['/login']) instead of false. The router will then navigate to that URL. You can also pass query parameters to preserve the original destination.
Do guards run on initial page load?
Yes, when the app first loads and the router resolves the initial URL, guards on the matched route are executed. This ensures that deep links are protected as well.
Can I use a guard with a lazy-loaded module?
Yes, but to prevent the module from being downloaded, use canMatch (or canLoad in older versions) on the route that points to the lazy module. canActivate will still run, but only after the module is loaded.
How do I test a guard that uses an HTTP service?
Mock the HTTP service using Jasmine spies or Angular’s HttpClientTestingModule. Then, provide the mock in the guard’s constructor and test the guard’s logic with different mock responses.
Related
Formula used
in t | Finish time | min | 50 | 0 in d | Distance | km | 10 | 0 out pace | Average pace | min/km | t / d | 2 out speed | Average speed | km/h | d / (t / 60) | 2
Each line is one input (in, sel, list) or one result (out: label, unit, formula, decimals). How the calculators work
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.