Validação de formulário com ngModel - Angular
Como construir uma validação de formulário direto no template através do uso da diretiva ngModel.
Em um outro post, Validação de formulário utilizando o ReactiveFormsModule do Angular 2+, foi apresentado como podemos construir uma validação de formulário utilizando alguns recursos como FormGroup, FormControl, FormBuilder, entre outros. Neste texto vamos abordar uma outra forma de construir validação de formulários com o uso da diretiva ngModel.
Introdução
Considere o template abaixo:
<form (ngSubmit)="postar()">
<div>
<input type="text"
name="nome"
placeholder="Nome..." />
</div>
<div>
<button type="submit">
Enviar
</button>
</div>
</form>Quando o botão Enviar for clicado, o formulário sofrerá um post e o método postar() será chamado.
Neste exemplo não estamos enviando os dados do formulário para o método postar() e também não estamos validando nenhuma informação.
Para facilitar o trabalho e podermos pegar os dados dos campos do formulário e também fazer uma validação, vamos criar uma instância do FormControl e vinculá-lo ao input através do uso da diretiva ngModel.
Diretiva ngModel
O primeiro passo é garantir que o módulo do seu componente tenha importado o FormsModule. Neste artigo os códigos de exemplo serão incluídos nos componentes Exemplo1Component e Exemplo2Component que estão declarados no AppModule. Logo, temos que importar o FormsModule no AppModule conforme o exemplo abaixo:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import {
Exemplo1Component
} from './exemplos/exemplo1.component';
import {
Exemplo2Component
} from './exemplos/exemplo2.component';
/* ... (código não relevante) ... */
@NgModule({
imports: [
AppRoutingModule,
BrowserModule,
FormsModule,
],
declarations: [
AppComponent,
Exemplo1Component,
Exemplo2Component,
/* ... (código não relevante) ... */
],
bootstrap: [
AppComponent
]
})
export class AppModule { }
Vamos declarar um método dentro da classe para sabermos que o formulário foi postados e na sequência abordaremos seu template:
import { Component } from '@angular/core';
@Component({
templateUrl: './exemplo1.component.html'
})
export class Exemplo1Component {
postar() {
console.log('Post efetuado!');
}
}Agora voltamos a atenção para o template. Vamos adicionar a diretiva ngModel no input para criar um FormControl:
<form (ngSubmit)="postar()">
<div>
<input ngModel
name="nome"
placeholder="Nome..."
type="text" />
</div>
<div>
<button type="submit">
Enviar
</button>
</div>
</form>Para pegarmos a referência desta diretiva, ngModel, podemos escrever algo do tipo: #minhaVariavel="ngModel".
Lembre-se de que durante a criação de uma diretiva podemos preencher a propriedade exportAs para definir um nome, um nome que possa ser utilizado no template para pegarmos a instância da diretiva. No caso do ngModel é "ngModel".
Fonte: https://angular.io/api/core/Directive#exportas
To inspect the properties of the associatedFormControl(like the validity state), export the directive into a local template variable usingngModelas the key (ex:#myVar="ngModel"). You can then access the control using the directive'scontrolproperty. However, the most commonly used properties (likevalidanddirty) also exist on the control for direct access. See a full list of properties directly available inAbstractControlDirective.
https://angular.io/api/forms/NgModel#description
Seguindo a lógica acima:
<form (ngSubmit)="postar()">
<div>
<input ngModel
#formNome="ngModel"
name="nome"
placeholder="Nome..."
type="text" />
</div>
<div>
<button type="submit">
Enviar
</button>
</div>
</form>No template acima criamos um FormControl vinculado ao nosso único input e passamos sua referência para a variável formNome. Então vamos testar se realmente formNome: FormControl está configurado corretamente adicionando alguns trechos a mais:
<form (ngSubmit)="postar()">
<div>
<input ngModel
#formNome="ngModel"
name="nome"
placeholder="Nome..."
type="text" />
</div>
<div>
<button type="submit">
Enviar
</button>
</div>
</form>
<!-- Trecho para entendimento -->
<hr>
<div>
formNome.value:
{{formNome.value}}
</div>
<div>
formNome.valid:
{{formNome.valid}}
</div>
<div>
<!-- Para utilizar o "| json"
importe o CommonModule -->
formNome.errors:
{{formNome.errors | json}}
</div>Podemos observar no resultado abaixo que conseguimos acessar o valor do input bem como outras informações como valid e errors:

Iniciando a validação do input
Agora vamos evoluir um pouco mais e adicionar algumas validações e melhorias:
- O campo
nomeé obrigatório - O campo
nomedeve ter pelo menos 3 caracteres - O botão
Enviardeve ficar desabilitado caso o camponometenha um valor inválido
As alterações são efetuadas direto no template:
<form (ngSubmit)="postar()">
<div>
<input ngModel
#formNome="ngModel"
required
minlength="3"
name="nome"
placeholder="Nome..."
type="text" />
</div>
<div>
<!--
O botão é desabilitado
conforme o estado de
validação do formNome:
-->
<button [disabled]="formNome.invalid"
type="submit">
Enviar
</button>
</div>
</form>
<hr>
<div>
formNome.value:
{{formNome.value}}
</div>
<div>
formNome.valid:
{{formNome.valid}}
</div>
<div>
formNome.errors:
{{formNome.errors | json}}
</div>Veja no resultado abaixo que toda vez que digitamos algo no input:
- o
formNome.valuemuda conforme o valor doinput; - o
formNome.validouformNome.invalidmuda conforme o valor doinputfica válido/inválido. Veja que o botãoEnviaras vezes fica verde (habilitado) e as vezes cinza (desabilitado) - o
formNome.errorsmuda conforme os erros de validação mudam. Quando não há erros, ou seja, o valor é válido, oformNome.errorsapresentanull

Para finalizar precisamos:
- pegar o valor deste
inpute passar para o métodopostar - adicionar algumas mensagens de validação para ajudar o usuário
Alterando o método postar para receber um parâmetro (nome):
import { Component } from '@angular/core';
@Component({
templateUrl: './exemplo1.component.html'
})
export class Exemplo1Component {
postar(nome: string) {
console.log('Post efetuado:', nome);
}
}Alterando o template para incluir as mensagens de validação e também passar o valor do input no método postar:
<form (ngSubmit)="postar(formNome.value)">
<div>
<input ngModel
#formNome="ngModel"
required
minlength="3"
name="nome"
placeholder="Nome..."
type="text" />
</div>
<!--
Mensagens para o
usuário visualizar
o que está errado
na validação
-->
<div *ngIf="formNome.errors?.required"
class="erro-detalhes">
Campo obrigatório
</div>
<div *ngIf="formNome.errors?.minlength"
class="erro-detalhes">
Informe ao menos
{{formNome.errors.minlength.requiredLength}}
caracteres
</div>
<div>
<button [disabled]="formNome.invalid"
type="submit">
Enviar
</button>
</div>
</form>
Validação de múltiplos campos
Vamos adicionar mais um campo: sobrenome. Basicamente vamos duplicar o conteúdo do <input name="nome" ....
Na classe vamos adicionar mais um parâmetro no método postar para receber o sobrenome:
import { Component } from '@angular/core';
@Component({
templateUrl: './exemplo2.component.html'
})
export class Exemplo2Component {
postar(nome: string,
sobrenome: string) {
console.log('Valor formulário',
nome,
sobrenome);
}
} No template duplicamos toda a lógica envolvendo o input nome e criamos o input sobrenome:
<form (ngSubmit)="postar(formNome.value, formSobrenome.value)">
<!-- Campo Nome -->
<div>
<input ngModel
#formNome="ngModel"
required
minlength="3"
name="nome"
placeholder="Nome..."
type="text" />
</div>
<div *ngIf="formNome.errors?.required"
class="erro-detalhes">
Campo obrigatório
</div>
<div *ngIf="formNome.errors?.minlength"
class="erro-detalhes">
Informe ao menos
{{formNome.errors.minlength.requiredLength}}
caracteres
</div>
<!-- Campo Sobrenome
Para ficar levemente diferente
o minlength foi alterado para 2 -->
<div>
<input ngModel
#formSobrenome="ngModel"
required
minlength="2"
name="sobrenome"
placeholder="Sobrenome..."
type="text" />
</div>
<div *ngIf="formSobrenome.errors?.required"
class="erro-detalhes">
Campo obrigatório
</div>
<div *ngIf="formSobrenome.errors?.minlength"
class="erro-detalhes">
Informe ao menos
{{formSobrenome.errors.minlength.requiredLength}}
caracteres
</div>
<div>
<button [disabled]="formNome.invalid || formSobrenome.invalid"
type="submit">
Enviar
</button>
</div>
</form>Veja que apenas duplicamos os inputs ajustando os respectivos nomes. Aqui temos alguns pontos importantes:
- Agora o botão
Enviaranalisa dois valores ao invés de um para saber se deve ficar desabilitado ou habilitado - No
(ngSubmit)do formulário estamos passando dois campos ao invés de 1
Imagine que este formulário poderá crescer contento diversos campos. Ficará um pouco inviável seguir nesta linha. Para resolver este problema vamos utilizar um FormGroup no <form> através da sintaxe #minhaVariavel="ngForm".
Vamos alterar o parâmetro do método postar. Agora receberá um objeto contendo um nome e um sobrenome:
import { Component } from '@angular/core';
@Component({
templateUrl: './exemplo2.component.html'
})
export class Exemplo2Component {
postar(dados: { nome: string, sobrenome: string }) {
console.log('Valor formulário', dados);
}
} No template as alterações são pequenas:
- Incluir o
FormGroupno<form> - Ajustar a chamada do método
postar - Ajustar o
[disabled]do botãoEnviar
<form #meuForm="ngForm"
(ngSubmit)="postar(meuForm.value)">
<!-- ...(código ocultado)... -->
<div>
<button [disabled]="meuForm.invalid"
type="submit">
Enviar
</button>
</div>
</form>
Resultado:

Criando estilos baseados no estado
Agora que o formulário está funcionando corretamente, vamos focar no estilo visual.
Repare na imagem acima que há uma barra à esquerda do formulário que muda de cor conforme seu estado (válido/inválido). Conseguimos chegar neste efeito utilizando algumas classes CSS (tabela abaixo) que o Angular injeta tanto no <form> quando nos inputs que recebem o ngModel.
| State | Class if true |
Class if false |
|---|---|---|
| The control has been visited | ng-touched |
ng-untouched |
| The control's value has changed | ng-dirty |
ng-pristine |
| The control's value is valid | ng-valid |
ng-invalid |
Então para saber se o formulário está válido ou inválido podemos utilizar ng-valid e ng-invalid conforme o CSS abaixo:
form.ng-valid {
border-left: 4px solid #4CAF50;
}
form.ng-invalid {
border-left: 4px solid #f44336;
}Veja na imagem abaixo para entender melhor:

Considerações
Este recurso é extremamente útil para criar validações de forma bem rápida e eficiente. Não cheguei a abordar o two way data binding ([(ngModel)]="variavel"), mas caso tenha interesse em saber mais sobre o assunto: https://angular.io/guide/two-way-binding.
Caso você não esteja tão familiarizado com validações utilizando Angular, sugiro dar uma lida nestes posts:
- https://consolelog.com.br/validacao-de-formulario-utilizando-o-reactiveformsmodule-do-angular-2/
- https://consolelog.com.br/validacao-componente-customizado-angular/

Links:
