Skip to content

Angular Route Guards

Route guards let you control whether navigation to (or away from) a route is allowed, protecting authenticated pages or confirming unsaved changes. This lesson covers the main guard types using the modern functional approach.

What Is a Route Guard?

A route guard is a function (or, in classic Angular, a class) that Angular's router calls before activating, deactivating, or matching a route. Returning true allows the navigation to proceed; returning false (or a UrlTree redirect) blocks it.

Modern Angular favors functional guards, plain functions using inject(), over class-based guards implementing an interface, since they involve less boilerplate.

export const authGuard: CanActivateFn = (route, state) => {
  const auth = inject(AuthService);
  const router = inject(Router);

  if (auth.isLoggedIn()) return true;

  return router.createUrlTree(['/login'], {
    queryParams: { redirectTo: state.url },
  });
};

// route configuration
{ path: 'dashboard', component: DashboardComponent, canActivate: [authGuard] }

Returning a UrlTree (via createUrlTree) redirects the user instead of just blocking navigation with false.

Guard Types

canActivate: [authGuard]        // can this route be entered?
canActivateChild: [childGuard]  // can child routes under this one be entered?
canDeactivate: [unsavedGuard]   // can we leave the current route?
canMatch: [featureFlagGuard]    // should this route even be considered a match?
  • canActivate runs before a route is entered; returning false/redirect cancels navigation.
  • canDeactivate runs before leaving a route, useful for "unsaved changes" confirmation prompts.
  • canMatch decides whether a route definition should be considered a match at all, useful for feature flags.
  • All guard types support returning a boolean, a UrlTree, or an Observable/Promise resolving to either.

Route Guards Cheatsheet

The functional guard types and what each one controls.

Guard Question It Answers
canActivate Can the user enter this specific route?
canActivateChild Can the user enter any child route under this one?
canDeactivate Can the user leave the current route right now?
canMatch Should this route definition even be considered a match?
Return true Allow the navigation
Return false Block the navigation silently
Return UrlTree Redirect somewhere else instead of blocking

Protecting Routes With canActivate

The most common guard use case is blocking access to authenticated-only routes and redirecting anonymous users to a login page, optionally remembering where they were trying to go.

export const authGuard: CanActivateFn = (route, state) => {
  const auth = inject(AuthService);
  const router = inject(Router);
  return auth.isLoggedIn() || router.createUrlTree(['/login']);
};

export const routes: Routes = [
  { path: 'account', component: AccountComponent, canActivate: [authGuard] },
];

Confirming Unsaved Changes With canDeactivate

canDeactivate guards run before the router leaves the currently active route, a natural place to warn users about unsaved form changes before they navigate away accidentally.

export interface HasUnsavedChanges {
  hasUnsavedChanges(): boolean;
}

export const unsavedChangesGuard: CanDeactivateFn<HasUnsavedChanges> = (component) => {
  if (!component.hasUnsavedChanges()) return true;
  return confirm('You have unsaved changes. Leave anyway?');
};

{ path: 'edit/:id', component: EditFormComponent, canDeactivate: [unsavedChangesGuard] }

Conditionally Matching Routes With canMatch

canMatch runs before the router even considers a route a candidate match, useful for feature-flagged routes: if the guard returns false, the router tries the next matching route definition (like a fallback) instead of just blocking navigation.

export const betaFeatureGuard: CanMatchFn = () => {
  const flags = inject(FeatureFlagService);
  return flags.isEnabled('new-dashboard');
};

export const routes: Routes = [
  { path: 'dashboard', component: NewDashboardComponent, canMatch: [betaFeatureGuard] },
  { path: 'dashboard', component: LegacyDashboardComponent },
];

Common Mistakes

  • Returning false from a guard without redirecting anywhere, leaving users on a confusing, unchanged page.
  • Putting authentication *checks* in every component's ngOnInit instead of a single reusable canActivate guard.
  • Forgetting that canDeactivate needs to be attached to the route being left, not the route being entered.
  • Writing class-based guards for new code instead of the simpler, modern functional guard style.

Key Takeaways

  • Route guards control whether navigation to, from, or matching a route is allowed to proceed.
  • canActivate/canActivateChild protect entering routes; canDeactivate protects leaving them; canMatch controls route matching itself.
  • Modern Angular favors functional guards using inject() over class-based guard implementations.
  • Guards can return a boolean or a UrlTree to redirect instead of just blocking navigation.

Pro Tip

When blocking navigation, prefer returning a UrlTree (via router.createUrlTree()) over a plain false, it gives users a clear next step (like a login page) instead of leaving them stuck with no explanation.