Problem
I’m using a CanDeactivate guard in Angular to prompt the user before navigating away from a form with unsaved changes. It works fine for normal route changes (e.g., clicking a link), but it breaks when the user presses the browser back button.
Scenario
Let’s say my routing flow is:
/home → /user → /edit
- User is on
/editwith unsaved changes. - Presses the browser back button.
- A confirmation dialog is shown via
CanDeactivate. - If user cancels, the route stays the same (correct).
- But when they press back again and confirm, it navigates two steps back to
/home, skipping/user.
What I Tried
I implemented a CanDeactivate guard like this:
export class YourFormComponent implements CanComponentDeactivate, OnInit, AfterViewInit {
hasUnsavedChanges = false;
// Implement the canDeactivate method
canDeactivate(): Observable<boolean> | boolean {
if (!this.hasUnsavedChanges) {
return true;
}
const confirmLeave = window.confirm('You have unsaved changes. Leave anyway?');
return confirmLeave;
}
}
route.ts
import { Routes } from '@angular/router';
import { YourFormComponent } from './your-form.component';
import { ConfirmLeaveGuard } from './confirm-leave.guard';
const routes: Routes = [
{
path: 'form',
component: YourFormComponent,
canDeactivate: [ConfirmLeaveGuard]
}
];
confrim-leave.guard.ts
import { inject } from '@angular/core';
import { CanDeactivateFn } from '@angular/router';
import { Observable } from 'rxjs';
import { Location } from '@angular/common';
export interface CanComponentDeactivate {
canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}
export const ConfirmLeaveGuard: CanDeactivateFn<CanComponentDeactivate> = (component: any, currentRoute, currentState, nextState) => {
const location = inject(Location);
const result = component.canDeactivate();
// If it's a boolean value
if (typeof result === 'boolean') {
if (!result) {
location.replaceState(window.location.pathname); // Restore URL
}
return result;
}
// If it's an Observable or Promise
if (result instanceof Observable || result instanceof Promise) {
return new Promise(resolve => {
Promise.resolve(result).then(confirmed => {
if (!confirmed) {
location.replaceState(window.location.pathname); // Restore URL
}
resolve(confirmed);
});
});
}
return true;
};
I also tried using Location.replaceState() or Location.go() inside the guard to restore the history stack, but it still misbehaves when using the back button.
Question
How can I correctly handle the browser back button with CanDeactivate to prevent double navigation or skipped routes?
Any advice or examples would be appreciated.