CanDeactivate: Confirmando se o usuário deseja abandonar a página e descartar alterações
Tags: angular
Published: 2019-09-25 02:08:26

###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**.

```typescript
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:*

```typescript
        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:

```typescript
@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:

```typescript
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:

```typescript
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](https://stackblitz.com/edit/fdsc-can-deactivate?embed=1&file=src/app/formulario/formulario.component.ts).

{% stackblitz fdsc-can-deactivate %}