← Back to Article List         
What is CanMatch in Angular?

What is CanMatch in Angular?

Published on 30 Sep 2026     12 min read Angular
Route Guards

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

  • CanMatch determines 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
  • CanMatch is different from CanActivate.
  • 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

CanMatch is an Angular route guard that determines whether a route configuration should be matched by the Router. In modern Angular, it is implemented using CanMatchFn. If it returns true, Angular can use the route. If it returns false, Angular skips that route and looks for another matching route. This makes CanMatch useful 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>

&nbsp;

<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 dashboard route 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?”