Problema
Estou usando um CanDeactivate
protetor no Angular para avisar o usuário antes de sair de um formulário com alterações não salvas. Funciona bem para alterações de rota normais (por exemplo, clicar em um link), mas trava quando o usuário pressiona o botão Voltar do navegador .
Cenário
Digamos que meu fluxo de roteamento seja:
/home → /usuário → /editar
- O usuário está conectado
/edit
com alterações não salvas. - Pressiona o botão Voltar do navegador .
- Uma caixa de diálogo de confirmação é exibida via
CanDeactivate
. - Se o usuário cancelar , a rota permanece a mesma (correta).
- Mas quando eles pressionam novamente e confirmam , ele navega dois passos para trás
/home
, pulando/user
.
O que eu tentei
Implementei uma CanDeactivate
guarda como esta:
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;
}
}
rota.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;
};
Também tentei usar Location.replaceState() ou Location.go() dentro do guarda para restaurar a pilha do histórico, mas ele ainda funciona mal ao usar o botão Voltar.
Pergunta Como posso manipular corretamente o botão Voltar do navegador com CanDeactivate para evitar navegação dupla ou rotas ignoradas?
Qualquer conselho ou exemplo será apreciado.
Exemplo de Stackblitz
Imagem de trabalho - https://s6.imgcdn.dev/YjXe3M.gif
Usando seu StackBlitz, talvez eu tenha uma solução. Tente substituí-lo
canDeactivateGuard
por esta versão.CanDeactivate
A proteção do Angular não impede diretamente a navegação de retorno da maneira que desejamos. A proteção apenas cancela a navegação para a nova rota, mas não a interrompe. Portanto, parece que o navegador está "mantendo" o retorno anterior e, quando você cancela novamente, ele adiciona outro retorno a ele, e é por isso que ele retorna dois passos.Esta solução provavelmente não é perfeita, mas espero que possa ajudar você a chegar ao lugar certo.
Aqui está um exemplo menos complexo de
canDeactivate
. Podemos reutilizá-locanDeactivate
em vários componentes usando odecorator
padrão.Podemos definir o decorador para adicionar a
canActivate
função à nossa classe.Como você pode ver neste código, usamos
CanDeactivateContact
como classe padrãocanDeactivate
, mas podemos personalizá-la passando outra classe.Na função decoradora, simplesmente adicionamos o
canDeactivate
método de uma classe para outra.Depois disso, usamos
HostListener
o método _event_ específico para o evento _window:beforeunload
, que não podemos chamar,canDeactivate
pois não adicionamos a propriedade na classe. Portanto, podemos usardot syntax
(this['canDeactivate'](this);
) e chamar este método.Demonstração do Stackblitz