Angular CanDeactivate + browser back button breaks history after canceling navigation twice
05:36 26 Dec 2025

I have an Angular (standalone) application with a CanDeactivate guard that prevents navigation when a form is dirty.

Reproduction steps:

  1. Navigate to User Management

  2. Click on a user (e.g. “Alice”)

  3. Click Edit roles

  4. Modify the form so it becomes dirty

  5. Click the browser back button (Chrome)

A confirmation dialog is shown by the CanDeactivate guard.

  • If I click Cancel, navigation is correctly prevented and I stay on the page.

  • If I repeat the same action (browser back → Cancel) one more time, the browser history is now empty.

  • After that, the browser back button no longer works (history length is 0).

Expected behavior:
Canceling navigation via CanDeactivate should not consume browser history entries, even when done multiple times.

Actual behavior:
Each canceled browser back navigation seems to consume one history entry.

Guard implementation:

const canLeaveRoles: CanDeactivateFn = (component) => {
  if (!component.dirty) return true;
  return confirm('You have unsaved changes. Leave anyway?');
};

Notes:

  • In-app back buttons use Location.back()

  • The issue occurs only when using the browser back button

  • This happens consistently in Chrome

Question:
Is this expected behavior of Angular Router / browser history?
If so, what is the recommended way to prevent browser history from being consumed when navigation is canceled?

stackblitz demo

import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import {
  Routes,
  provideRouter,
  RouterOutlet,
  RouterLink,
  ActivatedRoute,
  CanDeactivateFn,
} from '@angular/router';
import { Location, NgFor } from '@angular/common';
import { FormsModule } from '@angular/forms';

/* -------------------- App Root + Sidenav -------------------- */

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, RouterLink],
  template: `
    
  `,
  styles: [
    `
    .layout { display: flex; height: 100vh; font-family: Arial; }
    .sidenav { width: 200px; padding: 16px; border-right: 1px solid #ddd; background: #f7f7f7; }
    .sidenav a { display: block; padding: 8px 0; text-decoration: none; color: #333; }
    .sidenav a.active { font-weight: bold; }
    .content { padding: 16px; flex: 1; }
  `,
  ],
})
class AppComponent {}

/* -------------------- Management -------------------- */

@Component({
  standalone: true,
  imports: [NgFor, RouterLink],
  template: `
    

User Management

`, }) class ManagementComponent { users = [ { id: '1', name: 'Alice' }, { id: '2', name: 'Bob' }, { id: '3', name: 'Charlie' }, ]; } /* -------------------- User -------------------- */ @Component({ standalone: true, imports: [RouterLink], template: `

User {{ userId }}

Roles

Edit roles
`, }) class UserComponent { userId = this.route.snapshot.paramMap.get('userId'); constructor(private route: ActivatedRoute, private location: Location) {} back() { this.location.back(); } } /* -------------------- Roles -------------------- */ @Component({ standalone: true, imports: [FormsModule], template: `

Edit Roles



`, }) class RolesComponent { dirty = false; roles = { admin: false, editor: true, }; constructor(private location: Location) {} markDirty() { this.dirty = true; } save() { this.dirty = false; this.location.back(); } cancel() { this.location.back(); } } /* -------------------- Guard -------------------- */ const canLeaveRoles: CanDeactivateFn = (component) => !component.dirty || confirm('You have unsaved changes. Leave anyway?'); /* -------------------- Routes (with titles) -------------------- */ const routes: Routes = [ { path: '', redirectTo: 'management', pathMatch: 'full', }, { path: 'management', component: ManagementComponent, title: 'User Management', }, { path: 'management/:userId', component: UserComponent, title: (route) => `User ${route.paramMap.get('userId')}`, }, { path: 'management/:userId/roles', component: RolesComponent, title: 'Edit User Roles', canDeactivate: [canLeaveRoles], }, ]; /* -------------------- Bootstrap -------------------- */ bootstrapApplication(AppComponent, { providers: [provideRouter(routes)], });
javascript angular