DEV Community

Cover image for CanDeactivate: Confirmando se o usuário deseja abandonar a página e descartar alterações
Felipe Carvalho
Felipe Carvalho

Posted on • Edited on

CanDeactivate: Confirmando se o usuário deseja abandonar a página e descartar alterações

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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 { }
Enter fullscreen mode Exit fullscreen mode

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 }
];
Enter fullscreen mode Exit fullscreen mode

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;
    }
  };
}

Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)