What is CanMatch in Angular?
What is CanMatch?
CanMatch is an Angular route guard that determines whether a particular route configuration should be matched by the Angular Router.
In modern Angular, it is commonly implemented using:
CanMatchFn
The main question CanMatch answers is:
Should Angular use this route for the requested URL?
For example:
User requests /admin
↓
CanMatch guard
↓
Is user Admin?
/ \
Yes No
↓ ↓
Match Don't match
/admin this route
It is particularly useful with lazy-loaded routes, role-based routing, and feature flags.
Full Simple Sample Program
Suppose we have an Admin page that should only be matched for users with the Admin role.
auth.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private role = 'Admin';
isAdmin(): boolean {
return this.role === 'Admin';
}
}
admin.guard.ts
import { inject } from '@angular/core';
import {
CanMatchFn,
Router
} from '@angular/router';
import { AuthService } from './auth.service';
export const adminMatchGuard: CanMatchFn =
(route, segments) => {
const authService = inject(AuthService);
const router = inject(Router);
if (authService.isAdmin()) {
return true;
}
return router.createUrlTree(['/access-denied']);
};
admin.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-admin',
standalone: true,
template: `
<h2>Admin Page</h2>
<p>Welcome Admin.</p>
`
})
export class Admin {
}
access-denied.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-access-denied',
standalone: true,
template: `
<h2>Access Denied</h2>
<p>
You do not have permission to access this page.
</p>
`
})
export class AccessDenied {
}
app.routes.ts
import { Routes } from '@angular/router';
import { AccessDenied } from './access-denied';
import { adminMatchGuard } from './admin.guard';
export const routes: Routes = [
{
path: 'admin',
canMatch: [
adminMatchGuard
],
loadComponent: () =>
import('./admin')
.then(m => m.Admin)
},
{
path: 'access-denied',
component: AccessDenied
}
];
app.ts
import { Component } from '@angular/core';
import {
RouterLink,
RouterOutlet
} from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
imports: [
RouterLink,
RouterOutlet
],
template: `
<h1>My Application</h1>
<a routerLink="/admin">
Admin
</a>
<hr>
<router-outlet></router-outlet>
`
})
export class App {
}
app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes)
]
};
How Does CanMatch Work?
The important configuration is:
{
path: 'admin',
canMatch: [adminMatchGuard],
loadComponent: () =>
import('./admin')
.then(m => m.Admin)
}
When the user requests:
/admin
Angular finds the candidate route:
path: 'admin'
Before selecting that route as the match, Angular executes:
adminMatchGuard
If User Is Admin
Suppose:
private role = 'Admin';
The guard checks:
authService.isAdmin()
and returns:
return true;
Flow:
User → /admin
↓
Candidate route found
↓
canMatch
↓
adminMatchGuard
↓
isAdmin() = true
↓
Route matches
↓
Admin component loaded
↓
Admin Page
What Happens When CanMatch Returns false?
This is the most important point about CanMatch.
If a CanMatch guard returns:
return false;
Angular says:
Do not use this route configuration. Continue looking for another matching route.
It does not simply mean "show Access Denied."
For example:
export const routes: Routes = [
{
path: 'dashboard',
canMatch: [adminGuard],
loadComponent: () =>
import('./admin-dashboard')
.then(m => m.AdminDashboard)
},
{
path: 'dashboard',
loadComponent: () =>
import('./user-dashboard')
.then(m => m.UserDashboard)
}
];
Notice both routes use:
/dashboard
If adminGuard returns true:
/dashboard
↓
First route
↓
adminGuard = true
↓
AdminDashboard
If adminGuard returns false:
/dashboard
↓
First route
↓
adminGuard = false
↓
Don't use first route
↓
Router checks next route
↓
Second /dashboard route
↓
UserDashboard
This behavior makes CanMatch useful for conditional route selection.
false vs Redirect
These are different.
Return false
return false;
Means:
Don't match this route; Angular may try another matching route.
Return a redirect
return router.createUrlTree(['/access-denied']);
Means:
Redirect the navigation to
/access-denied.
So:
false
↓
Try another route
UrlTree
↓
Redirect somewhere else
This is a very important interview point.
CanMatch with Lazy Loading
CanMatch is particularly useful with lazy-loaded routes.
For example:
{
path: 'admin',
canMatch: [adminMatchGuard],
loadChildren: () =>
import('./admin/admin.routes')
.then(m => m.ADMIN_ROUTES)
}
Conceptually:
User requests /admin
↓
Can this route match?
↓
CanMatch guard
↓
Allowed?
/ \
Yes No
↓ ↓
Use route Don't use route
↓
Lazy feature needed
This lets you decide whether that route configuration is eligible before Angular proceeds with it.
CanActivate vs CanMatch
This is the main interview distinction.
CanActivate |
CanMatch |
|---|---|
| Controls whether a matched route can be activated | Controls whether a route configuration should match |
| Runs as part of route activation | Participates in route matching |
CanActivateFn |
CanMatchFn |
| Common for protected pages | Useful for conditional route selection/lazy routes |
false cancels navigation |
false makes Router try other matching routes |
Think:
CanMatch
↓
"Should this route be selected?"
CanActivate
↓
"Can the selected route be activated?"
Easy Example to Understand the Difference
Suppose:
/admin
Using CanMatch
{
path: 'admin',
canMatch: [adminGuard],
loadComponent: () =>
import('./admin')
.then(m => m.Admin)
}
Angular asks:
Should I consider this /admin route
as a valid match?
Using CanActivate
{
path: 'admin',
canActivate: [adminGuard],
loadComponent: () =>
import('./admin')
.then(m => m.Admin)
}
Angular asks:
This route matched.
Can I activate it?
The distinction is:
URL
↓
Route matching
↓
CanMatch
↓
Route selected
↓
CanActivate
↓
Component activated
This is a simplified mental model, but it captures the important difference.
CanMatch for Feature Flags
Another common use is feature-based routing.
Suppose your company is developing a new Reports feature:
export const reportsGuard: CanMatchFn = () => {
const featureService = inject(FeatureService);
return featureService.isEnabled('newReports');
};
Route:
{
path: 'reports',
canMatch: [reportsGuard],
loadComponent: () =>
import('./new-reports')
.then(m => m.NewReports)
}
If the feature is enabled:
/reports
↓
CanMatch = true
↓
New Reports
If disabled:
/reports
↓
CanMatch = false
↓
Router does not use this route
What Parameters Does CanMatchFn Receive?
A functional CanMatch guard looks like:
export const myGuard: CanMatchFn =
(route, segments) => {
// logic
};
It receives:
route
Information about the route configuration.
route.path
route.data
segments
The URL segments being considered.
For example, for:
/admin/users
the guard can inspect the relevant URL segments during matching.
What Can CanMatch Return?
Common results include:
Match route
return true;
Do not match this route
return false;
Redirect
return router.createUrlTree(['/login']);
It can also resolve asynchronously using:
Promise
Observable
Key Points
CanMatchdetermines whether Angular Router should match a route configuration.- Modern Angular uses:
CanMatchFn
- Configure it using:
canMatch: [adminMatchGuard]
true→ route may be matched.false→ this route is not used; Angular can try another matching route.- A redirect result can send the user to another route.
- It is useful for:
- Role-based route selection
- Feature flags
- Conditional routes
- Lazy-loaded features
CanMatchis different fromCanActivate.- Like other client-side guards, it does not replace backend authorization.
Interview Questions and Answers
1. What is CanMatch?
CanMatch is an Angular route guard that determines whether a particular route configuration should be matched by the Router.
2. How do you implement CanMatch in modern Angular?
Use CanMatchFn:
export const adminGuard: CanMatchFn = () => {
const authService = inject(AuthService);
return authService.isAdmin();
};
Then:
{
path: 'admin',
canMatch: [adminGuard],
loadComponent: () =>
import('./admin')
.then(m => m.Admin)
}
3. What happens if CanMatch returns true?
Angular can use that route as a match and continue navigation.
4. What happens if CanMatch returns false?
Angular does not use that route configuration and continues looking for another route that can match the URL.
This is different from CanActivate, where false cancels the navigation.
5. What is the difference between CanMatch and CanActivate?
CanMatch
↓
Should this route MATCH?
CanActivate
↓
Can this matched route be ACTIVATED?
CanMatch affects route selection, while CanActivate controls activation of a selected route.
6. Can CanMatch be used with lazy loading?
Yes. It is particularly useful with lazy-loaded routes:
{
path: 'admin',
canMatch: [adminGuard],
loadChildren: () =>
import('./admin/admin.routes')
.then(m => m.ADMIN_ROUTES)
}
7. Does CanMatch secure the backend API?
No. It only controls Angular client-side routing. The ASP.NET Core API must independently enforce authentication and authorization.
Interview-ready answer
CanMatchis an Angular route guard that determines whether a route configuration should be matched by the Router. In modern Angular, it is implemented usingCanMatchFn. If it returnstrue, Angular can use the route. If it returnsfalse, Angular skips that route and looks for another matching route. This makesCanMatchuseful for conditional routing, feature flags, role-based routing, and lazy-loaded features.
Complete CanMatch Example — Admin vs User Dashboard
Below is a complete standalone Angular example.
The same URL:
/dashboard
will display:
Admin → AdminDashboard
User → UserDashboard
The important idea is that both routes have the same path, and CanMatch decides whether the first route should be selected.
Project Files
src/app/
│
├── app.ts
├── app.html
├── app.config.ts
├── app.routes.ts
│
├── auth.service.ts
├── admin.guard.ts
│
├── admin-dashboard.ts
└── user-dashboard.ts
│
src/
└── main.ts
1. auth.service.ts
For simplicity, we'll store the current role in a variable.
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private role = 'Admin';
isAdmin(): boolean {
return this.role === 'Admin';
}
setRole(role: string): void {
this.role = role;
}
getRole(): string {
return this.role;
}
}
2. admin.guard.ts
This is our CanMatch guard.
import { inject } from '@angular/core';
import { CanMatchFn } from '@angular/router';
import { AuthService } from './auth.service';
export const adminGuard: CanMatchFn = () => {
const authService = inject(AuthService);
return authService.isAdmin();
};
The important line is:
return authService.isAdmin();
If Admin:
true
If normal User:
false
3. admin-dashboard.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-admin-dashboard',
standalone: true,
template: `
<h2>Admin Dashboard</h2>
<p>Welcome Admin!</p>
<p>
You can manage users, reports and application settings.
</p>
`
})
export class AdminDashboard {
}
4. user-dashboard.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-user-dashboard',
standalone: true,
template: `
<h2>User Dashboard</h2>
<p>Welcome User!</p>
<p>
This is the normal user dashboard.
</p>
`
})
export class UserDashboard {
}
5. app.routes.ts
This is the most important file.
import { Routes } from '@angular/router';
import { AdminDashboard } from './admin-dashboard';
import { UserDashboard } from './user-dashboard';
import { adminGuard } from './admin.guard';
export const routes: Routes = [
{
path: 'dashboard',
canMatch: [adminGuard],
component: AdminDashboard
},
{
path: 'dashboard',
component: UserDashboard
},
{
path: '',
redirectTo: 'dashboard',
pathMatch: 'full'
}
];
Notice:
{
path: 'dashboard',
canMatch: [adminGuard],
component: AdminDashboard
},
{
path: 'dashboard',
component: UserDashboard
}
We intentionally have two routes with exactly the same path:
/dashboard
/dashboard
This allows us to clearly see what CanMatch does.
6. app.ts
We'll add buttons so you can switch between Admin and User while testing.
import { Component } from '@angular/core';
import { Router, RouterOutlet } from '@angular/router';
import { AuthService } from './auth.service';
@Component({
selector: 'app-root',
standalone: true,
imports: [
RouterOutlet
],
templateUrl: './app.html'
})
export class App {
constructor(
private authService: AuthService,
private router: Router
) {
}
loginAsAdmin(): void {
this.authService.setRole('Admin');
this.router.navigateByUrl('/dashboard');
}
loginAsUser(): void {
this.authService.setRole('User');
this.router.navigateByUrl('/dashboard');
}
getRole(): string {
return this.authService.getRole();
}
}
7. app.html
<h1>CanMatch Demo</h1>
<p>
Current Role:
<strong>{{ getRole() }}</strong>
</p>
<button (click)="loginAsAdmin()">
Login as Admin
</button>
<button (click)="loginAsUser()">
Login as User
</button>
<hr>
<router-outlet></router-outlet>
8. app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes)
]
};
9. main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { App } from './app/app';
import { appConfig } from './app/app.config';
bootstrapApplication(App, appConfig)
.catch(err => console.error(err));
How the Program Works
Initially we have:
private role = 'Admin';
Open:
/dashboard
Angular starts checking the route configuration from top to bottom.
The first candidate is:
{
path: 'dashboard',
canMatch: [adminGuard],
component: AdminDashboard
}
Angular executes:
adminGuard
The guard executes:
return authService.isAdmin();
Because:
role === 'Admin'
it returns:
true
Therefore:
/dashboard
↓
First dashboard route
↓
adminGuard
↓
true
↓
Route MATCHES
↓
AdminDashboard
You see:
Admin Dashboard
Welcome Admin!
Now Click "Login as User"
This executes:
this.authService.setRole('User');
Now:
role = 'User'
When Angular evaluates /dashboard again, it first checks:
{
path: 'dashboard',
canMatch: [adminGuard],
component: AdminDashboard
}
The guard executes:
authService.isAdmin()
which is now:
'User' === 'Admin'
Result:
false
Here's the critical part.
Angular does not say:
Navigation failed.
Instead it says:
This first
dashboardroute cannot match. Let me check the next route.
The next route is:
{
path: 'dashboard',
component: UserDashboard
}
There is no guard preventing it.
So:
/dashboard
↓
First route
↓
CanMatch
↓
false
↓
SKIP first route
↓
Check next route
↓
Second /dashboard
↓
UserDashboard
You see:
User Dashboard
Welcome User!
Why Route Order Matters
Angular checks routes in order.
We intentionally put:
{
path: 'dashboard',
canMatch: [adminGuard],
component: AdminDashboard
}
before:
{
path: 'dashboard',
component: UserDashboard
}
If you reverse them:
{
path: 'dashboard',
component: UserDashboard
},
{
path: 'dashboard',
canMatch: [adminGuard],
component: AdminDashboard
}
the first route already matches /dashboard.
So Angular would use UserDashboard without needing the second route.
Therefore:
Specific/conditional route
↓
FIRST
Fallback route
↓
SECOND
This Example Shows the Real Difference
With CanMatch
{
path: 'dashboard',
canMatch: [adminGuard],
component: AdminDashboard
},
{
path: 'dashboard',
component: UserDashboard
}
Admin:
/dashboard
↓
Admin route
↓
CanMatch = true
↓
AdminDashboard
User:
/dashboard
↓
Admin route
↓
CanMatch = false
↓
Skip
↓
User route
↓
UserDashboard
What If We Used CanActivate Instead?
Suppose you changed it to:
{
path: 'dashboard',
canActivate: [adminGuard],
component: AdminDashboard
},
{
path: 'dashboard',
component: UserDashboard
}
Conceptually, Angular has already matched the first route.
If the activation guard returns false:
/dashboard
↓
First route MATCHED
↓
CanActivate
↓
false
↓
Navigation CANCELLED
It does not use CanActivate as a signal to fall through and select the second /dashboard route.
That's why this example is ideal for understanding CanMatch:
CanMatch false
↓
"Skip this route and find another"
CanActivate false
↓
"Don't activate this matched route"
Final Memory Trick
/dashboard
↓
┌─────────────────┐
│ Admin Route │
│ CanMatch Guard │
└────────┬────────┘
↓
Is Admin?
/ \
YES NO
↓ ↓
AdminDashboard Skip Route
↓
Next /dashboard
↓
UserDashboard
CanMatch = “Is this the route Angular should choose?”
CanActivate = “Angular chose this route; can the user enter it?”