Validação de formulário utilizando o ReactiveFormsModule do Angular 2+
Começando a validação do formulário HTML da forma "tradicional"
Para quem está començando com Angular é natural pensar nos eventos como o keyup
para preencher uma variável e depois efetuar a validação, por exemplo:
<form>
<input type="text"
placeholder="Nome"
(keyup)="nome = $event.target.value" />
<input type="password"
placeholder="Senha"
(keyup)="senha = $event.target.value" />
<!-- ... -->
<button (click)="postar()">Postar</button>
<button>Reset</button>
</form>
…e na classe do componente algo do tipo:
@Component({ /* ... */})
export class MeuComponent {
nome: string;
senha: string;
// ...
postar() {
if(!nome || !senha) {
/* ... */
console.log('Formulário inválido');
return;
}
console.log('Formulário válido');
}
}
Com toda certeza esta lógica funcionaria mas daria um certo trabalho conforme avançamos no desenvolvimento desta validação. A final de contas, ainda faltam validar tamanho mínimo/máximo, e-mail, desabilitar o botão Postar caso o formulário esteja inválido, exibir mensagens de erro, enfim.
FormGroup & FormControl
No Angular 2+ há duas classes que nos ajudarão a trabalhar com a validação de formulários. Essas classes são FormGroup
e FormControl
.
Antes de entrar nos detalhes sobre cada uma delas vamos ver na prática como ficaria uma simples validação, se atente aos comentário no código:
import { Component } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
@Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
meuFormGroup: FormGroup;
constructor(private formBuilder: FormBuilder) {
// **************************************************
// Abaixo utilizamos o formBuilder para construir
// vários FormControls que fazem parte do formulário.
// Cada FormControl valida um input do formulário
// **************************************************
this.meuFormGroup = this.formBuilder.group({
// *********************************************
// O valor padrão deste formControl será Marcelo
// e os demais vazio
// *********************************************
nome: ['Marcelo', Validators.required],
senha: ['', Validators.required],
email: ['', [
Validators.required,
Validators.email
]]
});
}
postar() {
if (!this.meuFormGroup.valid) {
console.log("Formulário inválido");
return;
}
console.log("Formulário válido", this.meuFormGroup.value);
}
}
Na classe acima declaramos o meuFormGroup
que agrupa 3 campos:
nome
senha
email
O próximo passo é vincular o meuFormGroup
ao <form>
do template, para isto utilizamos o atributo [formGroup]="meuFormGroup"
no formulário e nos inputs utilizamos formControlName="<nome do formControl>"
coforme o seguinte código:
<form [formGroup]="meuFormGroup">
<input type="text"
placeholder="Nome"
formControlName="nome" />
<input type="email"
placeholder="Email"
formControlName="email" />
<input type="password"
placeholder="Senha"
formControlName="senha" />
<button (click)="postar()">
Postar
</button>
</form>
Após vincular o FormGroup
e seus FormControl
ao template fica fácil saber se o formulário é válido ou não, basta acessarmos a propriedade valid
conforme o método postar()
do AppComponent
mostra. Veja o resultado na imagem abaixo:
Repare que os dois primeiros cliques mostram no console que o formulário está inválido. Após preencher todos os campos os valores do formulário são printados no console.
Pegando os dados do formulário
Bom, já sabemos como verificar se o formulário de uma forma geral está válido ou não, mas como fazemos para pegar os valores dos inputs?
Para pegar os dados do formulário basta utilizar a propriedade value
do FormGroup
e será retornado um objeto com os valores, veja o exemplo abaixo:
console.log(this.meuFormGroup.value);
// Retorno:
// {
// email: "kj@gmail.com",
// lembrar: "",
// nome: "Marcelo",
// senha: "123"
// }
Habilitando e desabilitando o botão de Post de acordo com a validação do formulário
Como já sabemos identificar se o formulário está válido ou não, fica mais fácil de desabilitar/habilitar o botão Postar de acordo com a validação.
Para desabilitar o botão podemos utilizar a propriedade disable
do input
. Lembrando que oFormGroup
tem uma propriedade chamada valid: boolean
que retorna true
quando todos os componentes habilitados estão válidos, então podemos escrever o seguinte :
<form [formGroup]="meuFormGroup">
<input type="text"
placeholder="Nome"
formControlName="nome" />
<input type="email"
placeholder="Email"
formControlName="email" />
<input type="password"
placeholder="Senha"
formControlName="senha" />
<button (click)="postar()"
[disabled]="!meuFormGroup.valid">
Postar
</button>
</form>
Exibindo mensagens de erro
Para exibir as mensagens de erro, podemos no próprio template acessar o respectivo FormControl
e verificar se ele é válido/inválido. No caso de ser inválido podemos apresentar uma mensagem de erro da seguinte forma:
<form [formGroup]="meuFormGroup">
<input type="text"
placeholder="Nome"
formControlName="nome" />
<span *ngIf="meuFormGroup.controls.nome.errors">
Campo obrigatório
</span>
<input type="email"
placeholder="Email"
formControlName="email" />
<span *ngIf="meuFormGroup.controls.email.errors">
Campo obrigatório
</span>
<input type="password"
placeholder="Senha"
formControlName="senha" />
<span *ngIf="meuFormGroup.controls.senha.errors">
Campo obrigatório
</span>
<button (click)="postar()"
[disabled]="!meuFormGroup.valid">
Postar
</button>
</form>
Observação: para utilizar o *ngIf
é necessário importar o módulo CommonModule
.
Resultado:
Refinando as mensagens de erro
Repare (classse AppComponent
) que o campo email tem dois Validators, required
e email
. O primeiro valida a obrigatoriedade do campo e o segundo valida se o que o usuário digitou realmente é um e-mail:
// ...
email: ["", [Validators.required, Validators.email]],
// ...
Para identificar qual Validator emitiu o erro podemos utilizar a seguinte lógica no template:
<input type="email"
placeholder="Email"
formControlName="email" />
<span *ngIf="meuFormGroup.controls.email.errors?.required">
Campo obrigatório
</span>
<span *ngIf="meuFormGroup.controls.email.errors?.email">
E-mail inválido
</span>
Resultado:
Como exibir as mensagens de erro após o onBlur
Veja que até agora as mensagens de erro aparecem na tela logo após seu carregamento. O desejável seria que as mensagens de erro só fossem exibidas quando o usuário interagisse com o componente, por exemplo no evento onBlur
, ou seja, toda vez que o usuário retirasse o foco do componente (evento onBlur
) exibiríamos a mensagem caso seu valor fosse inválido.
Felizmente existe uma propriedade do FormControl
chamadatouched
. Esta propriedade é do tipo boolean
e seu valor inicial é false
. Sempre que o input dispara o eventoonBlur
a propriedade touched
do respectivo FormControl
recebe o valor true
. Então podemos aproveitá-la para exibir as mensagens de erro apenas quando o usuário interagir com o componente:
<form [formGroup]="meuFormGroup">
<input type="text"
placeholder="Nome"
formControlName="nome" />
<ng-container *ngIf="meuFormGroup.controls.nome.touched">
<span *ngIf="meuFormGroup.controls.nome.errors">
Campo obrigatório
</span>
</ng-container>
<input type="email"
placeholder="Email"
formControlName="email" />
<ng-container *ngIf="meuFormGroup.controls.email.touched">
<span *ngIf="meuFormGroup.controls.email.errors?.required">
Campo obrigatório
</span>
<span *ngIf="meuFormGroup.controls.email.errors?.email">
E-mail inválido
</span>
</ng-container>
<input type="password"
placeholder="Senha"
formControlName="senha" />
<ng-container *ngIf="meuFormGroup.controls.senha.touched">
<span *ngIf="meuFormGroup.controls.senha.errors">
Campo obrigatório
</span>
</ng-container>
<button (click)="postar()"
[disabled]="!meuFormGroup.valid">
Postar
</button>
</form>
Resultado:
Considerações
A utilização das classesFormControl
eFormGroup
ajudam bastante a construir formulários mais interativos com muito menos código.
Veja o exemplo completo em funcionamento com todo o código fonte neste link:
https://validando-formulario-angular-reactiveformsmodule.stackblitz.io
Observação
Se você está tentando utilizar o formControlName
em componentes customizados (<meu-componente formControlName="...">...
), será necessário implementar a classe ControlValueAccessor
, mas isto é assunto para outro artigo :)