typescript 87 lines · 3 tabs

Warn Users About Unsaved Changes With an Angular CanDeactivate Guard

Shared by codesnips Sep 2026
3 tabs
import { CanDeactivateFn } from '@angular/router';
import { Observable } from 'rxjs';

export interface HasUnsavedChanges {
  canDeactivate(): boolean | Observable<boolean>;
}

export const unsavedChangesGuard: CanDeactivateFn<HasUnsavedChanges> = (
  component
) => {
  if (!component || typeof component.canDeactivate !== 'function') {
    return true;
  }

  return component.canDeactivate();
};
3 files · typescript Explain with highlit

This snippet shows the canonical Angular pattern for preventing accidental navigation away from an edit form that has unsaved changes. Angular's CanDeactivate route guard is functional-first in modern versions, so the guard delegates the decision to the component itself rather than hard-coding form knowledge into routing config. The result is a guard that is completely generic and reusable across any editing screen.

In unsaved-changes.guard.ts, the guard is typed against a small HasUnsavedChanges interface that exposes a single canDeactivate() method. Because the guard is written with CanDeactivateFn, it is a plain function that Angular's dependency injection can call with the deactivating component instance. The guard simply returns component.canDeactivate(), which may be a boolean or an Observable<boolean>. Pushing the logic into the component keeps the guard from needing to know about form state, confirm dialogs, or async save flows — it just asks the component whether it is safe to leave.

In confirm-dialog.service.ts, a thin service wraps whatever confirmation mechanism the app uses. Here it returns an Observable<boolean> so the guard can suspend navigation until the user answers. Modeling the prompt as an observable rather than a synchronous window.confirm is what allows the pattern to scale to Material dialogs or custom modals without touching the guard.

In profile-edit.component.ts, the component implements HasUnsavedChanges. Its canDeactivate() first checks a saved flag and the reactive form's dirty state; if nothing is at risk it returns true immediately so navigation is instant. Only when the form is dirty does it defer to ConfirmDialogService, returning the observable that resolves to the user's choice. Marking the form pristine after a successful save via markAsPristine() ensures a saved form no longer triggers the prompt.

The key trade-off is that the guard trusts each component to implement canDeactivate() honestly, so the interface contract matters. A common pitfall is forgetting to reset dirty state after saving, which produces a spurious warning. This approach is worth reaching for on any form where losing edits is costly, and it composes cleanly with lazy-loaded routes.


Related snips

Share this code

Here's the card — post it anywhere.

Warn Users About Unsaved Changes With an Angular CanDeactivate Guard — share card
Link copied