In Angular's router, how does a canMatch guard returning false differ from a canActivate guard returning false?
answer
- matching phase versus activation phase
- skip this route, try the next
- same path, different component
- lazy children not fetched on rejection
basics
~20 scanMatch runs while the router matches the URL: false makes it skip that route config and try the next one, possibly a ** fallback. canActivate runs after a route has matched: false cancels the whole navigation.
solid answer
~40 sA `CanMatchFn` is called as `(route: Route, segments: UrlSegment[], currentSnapshot: PartialMatchRouteSnapshot)` during recognition. If it returns `false`, the router treats that `Route` as not matching and keeps looking, so a later route with the same path, or the `**` wildcard, can take the URL. A `canActivate` guard runs only after the router has built the target state; `false` cancels the navigation outright. That makes `canMatch` the tool for serving different components on one path by role or feature flag, and for gating a `loadChildren` section: a rejected user never triggers that navigation's download of the lazy routes. A `UrlTree` or `RedirectCommand` from either still redirects. `canMatch` replaced the deprecated `canLoad`, and since v22 its third `currentSnapshot` argument is required in the types, exposing params, data and the URL matched so far.
code
ts · 16 linesimport { inject } from '@angular/core';
import { CanMatchFn, Routes } from '@angular/router';
import { AuthService } from './auth.service';
export const isAdmin: CanMatchFn = () => inject(AuthService).hasRole('admin');
export const routes: Routes = [
{
path: 'admin',
canMatch: [isAdmin],
loadChildren: () => import('./admin/admin.routes').then((m) => m.ADMIN_ROUTES),
},
{ path: 'dashboard', canMatch: [isAdmin], loadComponent: () => import('./admin/admin-dashboard').then((m) => m.AdminDashboard) },
{ path: 'dashboard', loadComponent: () => import('./user/user-dashboard').then((m) => m.UserDashboard) },
{ path: '**', loadComponent: () => import('./not-found').then((m) => m.NotFound) },
];go deeper
Recall that canMatch false means try the next route, while canActivate false means the navigation stops.
Explain that canMatch runs during recognition, before loadChildren is fetched, and what currentSnapshot does and does not contain.
Choose canMatch for role-specific screens and hidden sections, and flag that preloading ignores it and that chunks are never access control.
Weigh hiding sections behind a 404 against explicit forbidden pages, and how route gating aligns with server authorization and bundle boundaries.
## Two different moments in a navigation Angular's router processes a navigation in stages. First it **recognizes** the URL: it walks the `Routes` configuration in order, matching path segments and loading lazy `loadChildren` configs as it goes, to build a `RouterStateSnapshot`. Then it runs **guards** on that snapshot (`canDeactivate`, then `canActivateChild` and `canActivate`), then resolvers, then activates components. `canMatch` belongs to the first stage; `canActivate` belongs to the second. That timing explains every difference between them. ## What false means for each | | `canMatch` | `canActivate` | |---|---|---| | Runs during | URL matching (recognition) | Guard checks after recognition | | Arguments | `route: Route`, `segments: UrlSegment[]`, `currentSnapshot` | `route: ActivatedRouteSnapshot`, `state: RouterStateSnapshot` | | `false` means | This route config does not match; try the next one | Cancel the whole navigation | | `UrlTree` / `RedirectCommand` | Redirect | Redirect | | Lazy `loadChildren` of a rejected route | Not fetched by this navigation | Already loaded during recognition | | Fires `GuardsCheckStart` / `GuardsCheckEnd` | No, it is part of recognition | Yes | ## Same path, different screens Because a rejected `canMatch` just moves on, two routes may share a path: 1. `{ path: 'dashboard', component: AdminDashboard, canMatch: [isAdmin] }` 2. `{ path: 'dashboard', component: UserDashboard }` An admin matches the first; everyone else falls through to the second. With `canActivate` in place of `canMatch`, the first route would match every user and non-admins would have their navigation cancelled instead of seeing the user dashboard. The same pattern serves feature flags and A/B variants. For an admin-only area, putting `canMatch` on the `admin` route has a further effect: a non-admin falls through to the `**` not-found route, so the app does not reveal that an admin section exists. Whether that is desirable (a 404 instead of a "forbidden" page) is a product decision. ## The currentSnapshot argument `canMatch` originally received only `route` and `segments`. Angular 21.2 added a third argument, `currentSnapshot`, of type `PartialMatchRouteSnapshot`, and v22 made it required in the `CanMatchFn` type and the `CanMatch` interface (a migration updates class implementations). It is a `Pick` of `ActivatedRouteSnapshot` holding what is known at matching time: - `routeConfig`, `url`, `params`, `paramMap`, `queryParams`, `queryParamMap`, `fragment`, `data`, `outlet`, `title`. - Not included: `parent`, `root`, `pathFromRoot`, `firstChild`, `children`, because the tree is not fully matched yet. - Resolved data and lazily loaded components are not available either, since resolvers and component loading happen later. This lets a `canMatch` guard decide on a path parameter (for example a tenant id) without parsing `segments` by hand. ## canMatch and lazy loading For a route with `loadChildren`, the router runs `canMatch` before it asks for the child config. If the guard rejects, this navigation never downloads that chunk. That is why `canMatch` replaced `canLoad`, which was deprecated in v15.1 and only ever blocked `loadChildren`. Two caveats keep this honest: - **Preloading ignores `canMatch`.** A preloading strategy such as `PreloadAllModules` skips `loadChildren` routes only when they declare `canLoad`; `canMatch` does not stop preloading. - **Chunks are not secrets.** Anyone can request a JavaScript file; code that must stay private must not ship to the browser, and data must be protected by the server. ## Common mistakes - **Expecting `canMatch` to cancel.** A team swaps `canActivate` for `canMatch` on `/admin` and is surprised that non-admins now see the not-found page instead of a sign-in redirect. If the intent is "redirect", return a `UrlTree` from `canMatch` rather than `false`. - **Forgetting the fallback.** With no later route and no `**` wildcard, a rejected `canMatch` leaves the URL unmatched, and the navigation fails with a no-match error instead of showing anything useful. - **Expensive checks.** `canMatch` may run for every navigation that reaches that point in the config, so cache the role or flag rather than calling the server on each evaluation. - **Reading parents from `currentSnapshot`.** Only the picked fields exist; parent and child routes are not part of the partial snapshot. ## Choosing between them - Use `canMatch` when rejection should mean "this route is not for you, find another": role-specific screens on one path, feature flags, hiding a section behind a not-found page. - Use `canActivate` when rejection should mean "stop, or go here instead" and the guard needs the full `ActivatedRouteSnapshot` and `RouterStateSnapshot`, including parent routes. - Both can redirect with a `UrlTree` or `RedirectCommand`, and both run in the route's injection context, so `inject()` works in either.
- In Angular, does a canMatch guard on a loadChildren route stop PreloadAllModules from downloading that chunk?No. The router's preloader skips a `loadChildren` route only when the route declares `canLoad`; `canMatch` is evaluated during navigation matching, not during preloading. If a chunk must not be preloaded for some users, use a custom preloading strategy that checks the condition, and remember the file is still publicly fetchable either way.
- In Angular, what happens when a canMatch guard returns a UrlTree instead of false?The router does not fall through to other routes. It treats the `UrlTree` (or a `RedirectCommand`) as a redirect, cancels the current navigation and starts a new navigation to the target, exactly as a redirect from `canActivate` would.
canMatch is a building directory that simply omits a floor you are not cleared for, so you follow the next listing that fits; canActivate is the locked door on the floor you already found, which just turns you away.
saying these in an interview costs you the question
- canMatch returning false cancels the navigation just like canActivate does.
- canMatch keeps a lazy chunk from ever being downloaded, including by preloading.
- Two routes with the same path are a configuration error in Angular.
- canLoad is still the recommended guard for lazy loadChildren routes.
- canMatch receives the full RouterStateSnapshot with parent and child routes.