CanDeactivate: Confirmando se o usuário deseja abandonar a página e descartar alterações
Felipe Carvalho
Posted on September 25, 2019
Introdução
Você já deve ter se deparado com algum pop-up informando que o formulário possui alterações e que se abandonar a página, elas serão descartadas.
Esquecer de salvar um formulário é relativamente comum, então, neste post demonstrarei uma implementação simples com uma funcionalidade que o próprio Angular disponibiliza, o CanDeactivate.
Resumo
Criaremos um guard que será aplicado a uma rota. Este guard terá uma interface que deverá ser implementada pelo nosso componente e essa implementação deverá retornar um true quando o usuário puder deixar a página e false para o contrário. Simples, não?
Vamos ao exemplo
O exemplo consiste em um formulário e uma navegação entre dois links. Quando o usuário tiver modificado algo no formulário e não salvá-lo e tentar ir para "Outra página", uma confirmação será emitida.
Criação do Guard
OnComponentDeactivate é a interface que os componentes implementarão, ela poderá retornar um boolean de forma síncrona ou assíncrona, ou um UrlTree.
import { ActivatedRouteSnapshot, CanDeactivate, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
export interface OnComponentDeactivate {
canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean | UrlTree;
}
export class CanDeactivateGuard implements CanDeactivate<OnComponentDeactivate> {
constructor() { }
canDeactivate(
component: OnComponentDeactivate,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState: RouterStateSnapshot
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return component.canDeactivate();
}
}
Obs.: Não é muito comum usar UrlTree com o CanDeactivate. Não acredito que isso seja o comportamento esperado, mas, retornando um UrlTree, o Angular tenta novamente "desativar" a página atual, o que acaba gerando um loop infinito. A única solução que encontrei, foi com a seguinte verificação dentro do método canDeactivate:
const response = component.canDeactivate();
if (response instanceof UrlTree
&& nextState.url == response.toString()) {
return true;
}
return response;
Incluindo o Guard nos providers
Esse Guard funcionará como um serviço e deverá estar na seção providers. Meu app.module ficou da seguinte forma:
@NgModule({
declarations: [
AppComponent,
FormularioComponent,
OutraPaginaComponent
],
imports: [
BrowserModule,
AppRoutingModule,
ReactiveFormsModule
],
providers: [
CanDeactivateGuard
],
bootstrap: [AppComponent]
})
export class AppModule { }
Aplicando o Guard à rota
Estamos quase lá. Também devemos definir quais rotas acionarão o CanDeactivateGuard. Em meu app-routing.module.ts, defini que o meu componente de formulário irá acioná-lo:
const routes: Routes = [
{ path: "", redirectTo: "/formulario", pathMatch: "full" },
{ path: "formulario", component: FormularioComponent, canDeactivate: [CanDeactivateGuard] },
{ path: "outra", component: OutraPaginaComponent }
];
Implementando a interface
Agora só resta implementar a interface em nosso componente. Neste exemplo, utilizei o método confirm do javascript, que após a resposta do usuário, retornará um boolean. Nada impede que você utilize uma modal mais bonita ou aplique qualquer outro tipo de lógica para permitir ou não a desativação do seu componente, desde que forneça um dos retornos esperados.
Verifiquei se o usuário fez alguma alteração no formulário através da propriedade dirty. Quando o formulário for salvo, eu reseto esse estado através do markAsPristine() e usuário não precisará mais confirmar se deseja sair da página:
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { OnComponentDeactivate } from '../can-deactivate.guard';
import { Observable } from 'rxjs';
import { UrlTree } from '@angular/router';
@Component({
selector: 'app-formulario',
templateUrl: './formulario.component.html',
styleUrls: ['./formulario.component.css']
})
export class FormularioComponent implements OnInit, OnComponentDeactivate {
form: FormGroup;
constructor(private router: Router) { }
ngOnInit() {
this.form = new FormGroup({
"nome": new FormControl(null, [Validators.required, Validators.minLength(3)])
});
}
salvar() {
this.form.markAsPristine();
}
canDeactivate(): boolean | Observable<boolean> | Promise<boolean> | UrlTree {
if (this.form.dirty) {
return confirm("As alterações no formulário não foram salvas e serão descartadas, deseja prosseguir?");
} else {
return true;
}
};
}
Vamos ver funcionando?
Insira qualquer coisa no campo do formulário e tente navegar para "Outra página".
Caso não esteja vendo o exemplo abaixo, clique aqui.
Posted on September 25, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.