Custom Validators com ReactiveForms - síncronos e assíncronos!
Tags: angular
Published: 2019-07-19 22:50:12

##Introdução
**ReactiveForms** nos permite criar validações customizadas que alterarão o estado do formulário da mesma forma que as validações comuns o fazem, sejam elas síncronas ou assíncronas.
Demonstrarei como criá-las baseado no exemplo que criei no post anterior: [Introdução aos Reactive Forms!](https://dev.to/felipedsc/introducao-aos-reactive-forms-3ehl)

---

##Validação síncrona
Foi criada uma função de validação chamada **senhasProibidas** que recebe um **FormControl**, que será o controle onde aplicaremos a função.
Ela basicamente está validando se a senha é **12345** e retornando uma estrutura que o formulário identificará como erro. Mais abaixo explicarei ponto a ponto.

```typescript
senhasProibidas(control: FormControl): { [s: string]: boolean } {
  if (control.value === "12345") {
    return { 'senhaProibida': true };
  } else {
    return null;
  }
}
```

Criado o método de validação, ele pode ser aplicado no controle que criamos ao instanciar o formulário. **Repare que passamos a função senhasProibidas no mesmo array que as validações comuns**.
```typescript
this.usuarioForm = new FormGroup({
  "nome": new FormControl("João", [Validators.required]),
  "endereco": new FormControl(),
  "acesso": new FormGroup({
    "email": new FormControl(null, [Validators.required, Validators.email]),
    "senha": new FormControl(null, [Validators.required, Validators.minLength(3), this.senhasProibidas])
  }),
  "genero": new FormControl("M", [Validators.required]),
  "estadoCivil": new FormControl(null, [Validators.required]),
  "telefones": new FormArray([])
});
```

####Mas o que é esse retorno **{ [s: string]: boolean }**?!
Quando definimos uma propriedade entre [colchetes], criamos a possibilidade de informar qualquer chave para aquele objeto, isso também pode ser utilizado como um "truque" para permitir uma classe receber qualquer propriedade, sem que o typescript acuse erro. Ao invés de criar a estrutura do objeto no retorno, poderíamos também criar uma classe, sem problema algum:

```typescript
export class ChaveValor {
  [s: string]: boolean;
}
```

####Porque retornamos null quando o valor é válido?
Quando detectamos erro, retornamos um chave-valor **"senhaProibida": true**, conforme o que foi estabelecido na declaração da função.
Porém, quando o valor está OK, a função retorna null ao invés de **'senhaProibida': false**. É necessário retornar null pois é assim que o Angular interpreta que não houve erros naquela validação.

```typescript
  if (control.value === "12345") {
    return { 'senhaProibida': true };
  } else {
    return null;
  }
```

Exibi a estrutura criada com console.log para visualizarmos como ocorre a validação. 
Quando retornamos null, temos o seguinte estado:

![](https://thepracticaldev.s3.amazonaws.com/i/psvblt12w37e19k17xjp.PNG)

Se temos **key: true**, o Angular já identifica que o controle está inválido:

![](https://thepracticaldev.s3.amazonaws.com/i/7n6plmubf92nldlpjeqc.PNG)

Agora, se temos **key: false**, o controle está detectando que há algo dentro de **errors** e, apesar de querermos sinalizar que o controle está válido, ao passar algo para a propriedade, o Angular considera o controle inválido:

![](https://thepracticaldev.s3.amazonaws.com/i/7gh2aacxu2xnbcqln5ka.PNG)

---

##Validação assíncrona

Foi criada uma função que irá retornar uma chave e um boolean quando algum erro for detectado e nulo quando não houver erro, porém, este retorno deve ocorrer em forma de **Observable** (poderia ser **Promise**, também).
Na função, uma requisição ao servidor foi simulada com um **setTimeout** para verificar se o e-mail informado é **email@proibido.com**:

```typescript
emailsProibidos(control): Observable<{ [s: string]: boolean }> {
  return new Observable<{ [s: string]: boolean }>(observer => {
    setTimeout(() => {
      if (control.value == "email@proibido.com") {
        observer.next({ "emailProibido": true });
      } else {
        observer.next(null);
      }
      observer.complete();
    }, 2000);
  });
}

```

Após criar o método de validação, ele pode ser aplicado no controle que criamos ao instanciar o formulário.

```typescript
this.usuarioForm = new FormGroup({
  "nome": new FormControl("João", [Validators.required]),
  "endereco": new FormControl(),
  "acesso": new FormGroup({
    "email": new FormControl(null, [Validators.required, Validators.email], this.emailsProibidos),
    "senha": new FormControl(null, [Validators.required, Validators.minLength(3), this.senhasProibidas])
  }),
  "genero": new FormControl("M", [Validators.required]),
  "estadoCivil": new FormControl(null, [Validators.required]),
  "telefones": new FormArray([])
});
```

No controle **email**, o novo validador foi adicionado após os validadores "comuns". O terceiro parâmetro do **FormControl** recebe os validadores assíncronos:

![](https://thepracticaldev.s3.amazonaws.com/i/3zkd7karc1fc5ygsgt4v.png)

Agora, é importante observar que o formulário, enquanto espera a validação ser concluída, assume o estado **PENDING**. Relacionado a esse comportamento, no post anterior (mencionado no início deste), destaquei a importância de usar **!usuarioForm.valid** quando for desabilitar o botão submit.

---

##Vamos ver funcionando?
Informe a senha **12345** e o e-mail **email@proibido.com** e veja a validação atuando.
Recomendo abrir em uma nova aba e você pode fazer isso clicando [aqui](https://stackblitz.com/edit/fdsc-angular-reactive-forms-validators).

{% stackblitz fdsc-angular-reactive-forms-validators %}