Angular CanDeactivate + Browser Back Button causes double navigation or skips route

Discussão em 'Angular' iniciado por Karthik S, Abril 9, 2025.

  1. Karthik S

    Karthik S Guest

    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 /edit with 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.

    Continue reading...

Compartilhe esta Página