Artigo original: How to Validate Angular Reactive Forms

Introdução

Neste artigo, aprenderemos sobre a validação de formulários reativos em Angular. Criaremos um formulário simples de registro de usuário e implementaremos algumas validações integradas nele. Juntamente com as validações integradas, também vamos implementar algumas validações personalizadas para o formulário reativo.

Consideraremos as seguintes validações personalizadas para esta demonstração:

  • Verificar a disponibilidade do nome do usuário
  • Validar o padrão de senha
  • Validar a senha digitada em dois campos diferentes

Veja a aplicação em ação:

reactiveFormValidation

Pré-requisitos

  • Instalar o Visual Studio Code por este link
  • Instalar a versão mais atualizada da Angular CLI por este link

Código-fonte

Obtenha o código-fonte pelo GitHub.

Criando a aplicação em Angular

Navegue até a pasta onde deseja criar seu arquivo de projeto. Abra uma janela de comando e execute o comando mostrado abaixo:

ng new angular-forms-validation --routing=false --style=scss

Estamos especificando o comando para criar uma nova aplicação em Angular. A opção de criar o módulo de roteamento é definida como falsa e a extensão de arquivos de estilo é definida como scss. Este comando criará o projeto em Angular com o nome de angular-forms-validation.

Altere os diretórios, vá para o novo projeto e abra-o no Visual Studio Code usando os comandos mostrados abaixo:

cd angular-forms-validation 
code .

Instalando o Bootstrap

Execute o comando abaixo para instalar a biblioteca do Bootstrap na pasta do projeto:

npm install bootstrap --save

Adicione a seguinte definição de importação no arquivo styles.scss:

@import "~bootstrap/dist/css/bootstrap.css";

Criando o serviço de validação

Execute o comando abaixo para criar um novo serviço:

ng g s services\customvalidation

Este comando criará uma pasta nomeada serviços com dois arquivos — customvalidation.service.ts e customvalidation.service.spec.ts. Abra o arquivo customvalidation.service.ts e cole o código abaixo:

import { Injectable } from '@angular/core';
import { ValidatorFn, AbstractControl } from '@angular/forms';
import { FormGroup } from '@angular/forms';

@Injectable({
  providedIn: 'root'
})
export class CustomvalidationService {

  patternValidator(): ValidatorFn {
    return (control: AbstractControl): { [key: string]: any } => {
      if (!control.value) {
        return null;
      }
      const regex = new RegExp('^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$');
      const valid = regex.test(control.value);
      return valid ? null : { invalidPassword: true };
    };
  }

  MatchPassword(password: string, confirmPassword: string) {
    return (formGroup: FormGroup) => {
      const passwordControl = formGroup.controls[password];
      const confirmPasswordControl = formGroup.controls[confirmPassword];

      if (!passwordControl || !confirmPasswordControl) {
        return null;
      }

      if (confirmPasswordControl.errors && !confirmPasswordControl.errors.passwordMismatch) {
        return null;
      }

      if (passwordControl.value !== confirmPasswordControl.value) {
        confirmPasswordControl.setErrors({ passwordMismatch: true });
      } else {
        confirmPasswordControl.setErrors(null);
      }
    }
  }

  userNameValidator(userControl: AbstractControl) {
    return new Promise(resolve => {
      setTimeout(() => {
        if (this.validateUserName(userControl.value)) {
          resolve({ userNameNotAvailable: true });
        } else {
          resolve(null);
        }
      }, 1000);
    });
  }

  validateUserName(userName: string) {
    const UserList = ['ankit', 'admin', 'user', 'superuser'];
    return (UserList.indexOf(userName) > -1);
  }
}

O método patternValidator é usado para validar o padrão de senha em nosso formulário. O parâmetro para este método é o tipo AbstractControl, que é uma classe base para FormControl.

Usaremos uma expressão regular para validar a senha. Validaremos as quatro condições a seguir usando a expressão regular:

  • A senha deve ter um mínimo de oito caracteres.
  • Precisa ter pelo menos uma letra minúscula.
  • Precisa ter pelo menos uma letra maiúscula.
  • Precisa ter pelo menos um número.

Se a senha falhar na verificação pela regex, definiremos a propriedade invalidPassword como verdadeira.

O método MatchPassword é usado para comparar as senhas em dois campos. Este método aceitará dois parâmetros do tipo String. Estes parâmetros representam o nome dos campos a serem correspondidos. Pegaremos o  FormControl para esses dois campos e, em seguida, igualaremos os valores neles. Se os valores não coincidirem, definiremos a propriedade passwordMismatch como verdadeira.

O método userNameValidator é usado para verificar se o nome de usuário já foi usado anteriormente ou não. Este método aceitará um parâmetro do tipo AbstractControl. Verificaremos se o valor deste campo está presente em um array estático, UserList. Se o valor inserido pelo usuário já estiver presente, definiremos a  propriedade userNameNotAvailable como verdadeira.

Estamos usando a função setTimeout para executar esta verificação a cada dois segundos. Isso garantirá que o erro será acionado após dois segundos a partir do momento em que o usuário parar de digitar no campo.

Para simplificar este artigo, estamos usando um array estático para pesquisar a disponibilidade de nomes de usuário. Idealmente, deveria ser uma chamada de serviço ao servidor para pesquisar o valor em um banco de dados.

Criando o componente reactive-form

Execute o seguinte comando para criar o componente  reactive-form:

ng g c reactive-form

Abra reactive-form.component.ts e cole o código abaixo:

import { Component, OnInit } from '@angular/core';
import { Validators, FormGroup, FormBuilder } from '@angular/forms';
import { CustomvalidationService } from '../services/customvalidation.service';

@Component({
  selector: 'app-reactive-form',
  templateUrl: './reactive-form.component.html',
  styleUrls: ['./reactive-form.component.scss']
})
export class ReactiveFormComponent implements OnInit {

  registerForm: FormGroup;
  submitted = false;

  constructor(
    private fb: FormBuilder,
    private customValidator: CustomvalidationService
  ) { }

  ngOnInit() {
    this.registerForm = this.fb.group({
      name: ['', Validators.required],
      email: ['', [Validators.required, Validators.email]],
      username: ['', [Validators.required], this.customValidator.userNameValidator.bind(this.customValidator)],
      password: ['', Validators.compose([Validators.required, this.customValidator.patternValidator()])],
      confirmPassword: ['', [Validators.required]],
    },
      {
        validator: this.customValidator.MatchPassword('password', 'confirmPassword'),
      }
    );
  }

  get registerFormControl() {
    return this.registerForm.controls;
  }

  onSubmit() {
    this.submitted = true;
    if (this.registerForm.valid) {
      alert('Form Submitted succesfully!!!\n Check the values in browser console.');
      console.table(this.registerForm.value);
    }
  }
}

Criaremos a variável registerForm do tipo FormGroup. No método ngOnInit, estabeleceremos os controles para o formulário usando a classe FormBuilder. Todos os campos são definidos como um campo obrigatório para este formulário. Chamaremos o método userNameValidator do serviço usando a função bind.

Para o campo de senha, usaremos o método de composição para mesclar em vários validadores em uma única função. Também invocaremos o método MatchPassword e passaremos o nome de password e confirmPassword como parâmetros.

A propriedade registerFormControl retornará os controles de formulário. O método onSubmit imprimirá o conteúdo do formulário no console se o formulário for válido e enviado com sucesso.

Agora, abra reactive-form.component.html e cole nele o código a seguir:

<div class="container">
    <div class="row">
        <div class="col-md-8 mx-auto">
            <div class="card">
                <div class="card-header">
                    <h3>Angular Reactive Form</h3>
                </div>
                <div class="card-body">
                    <form class="form" [formGroup]="registerForm" (ngSubmit)="onSubmit()">
                        <div class="form-group">
                            <label>Name</label>
                            <input type="text" class="form-control" formControlName="name">
                            <span class="text-danger"
                                *ngIf="(registerFormControl.name.touched || submitted) && registerFormControl.name.errors?.required">
                                Name is required
                            </span>
                        </div>
                        <div class="form-group">
                            <label>Email</label>
                            <input type="text" class="form-control" formControlName="email">
                            <span class="text-danger"
                                *ngIf="(registerFormControl.email.touched || submitted) && registerFormControl.email.errors?.required">
                                Email is required
                            </span>
                            <span class="text-danger"
                                *ngIf="registerFormControl.email.touched && registerFormControl.email.errors?.email">
                                Enter a valid email address
                            </span>
                        </div>
                        <div class="form-group">
                            <label>User Name</label>
                            <input type="text" class="form-control" formControlName="username">
                            <span class="text-danger"
                                *ngIf="(registerFormControl.username.touched || submitted) && registerFormControl.username.errors?.required">
                                User Name is required
                            </span>
                            <span class="text-danger"
                                *ngIf="registerFormControl.username.touched && registerFormControl.username.errors?.userNameNotAvailable">
                                User Name is not available
                            </span>
                        </div>
                        <div class="form-group">
                            <label>Password</label>
                            <input type="password" class="form-control" formControlName="password">
                            <span class="text-danger"
                                *ngIf="(registerFormControl.password.touched || submitted) && registerFormControl.password.errors?.required">
                                Password is required
                            </span>
                            <span class="text-danger"
                                *ngIf="registerFormControl.password.touched && registerFormControl.password.errors?.invalidPassword">
                                Password should have minimum 8 characters, at least 1 uppercase letter, 1 lowercase
                                letter and 1 number
                            </span>
                        </div>
                        <div class="form-group">
                            <label>Confirm Password</label>
                            <input type="password" class="form-control" formControlName="confirmPassword">
                            <span class="text-danger"
                                *ngIf="(registerFormControl.confirmPassword.touched || submitted)&& registerFormControl.confirmPassword.errors?.required">
                                Confirm Password is required
                            </span>
                            <span class="text-danger"
                                *ngIf="registerFormControl.confirmPassword.touched && registerFormControl.confirmPassword.errors?.passwordMismatch">
                                Passwords doesnot match
                            </span>
                        </div>
                        <div class="form-group">
                            <button type="submit" class="btn btn-success">Register</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>

Criaremos um reactive-form e usaremos o card do Bootstrap para a estilização. O cabeçalho do nosso card terá um título, enquanto o corpo terá os campos do formulário. Vincularemos a propriedade formGroup da tag  <form> ao nome do nosso formulário, que é registerForm. O método onSubmit será invocado ao enviar o formulário. Também vincularemos a  propriedade formControlName de cada campo de entrada ao nome de controle do nosso FormGroup. Verificaremos se há erros nos controles do formulário e, em seguida, exibiremos a mensagem de erro de validação apropriada na tela.

Execute o seguinte comando para criar o componente nav-bar:

ng g c nav-bar

Abra nav-bar.component.html e cole o código abaixo:

<nav class="navbar navbar-expand-sm navbar-dark bg-dark fixed-top">
    <a class="navbar-brand" [routerLink]='["/"]'>Form Validation Demo</a>
    <div class="collapse navbar-collapse">
        <ul class="navbar-nav mr-auto">
            <li class="nav-item">
                <a class="nav-link" [routerLink]='["/reactive-form"]'>Reactive Form</a>
            </li>
        </ul>
    </div>
</nav>

Estamos adicionando o link de navegação ao componente da reactive-form na barra de navegação.

Atualizando o componente do aplicação

Abra o arquivo app.component.html e cole nele o código abaixo:

<app-nav-bar></app-nav-bar>
<div class="container">
  <router-outlet></router-outlet>
</div>

Atualizando o módulo da aplicação

Adicione o código a seguir no arquivo app.module.ts. Vamos importar o módulo de formulários e definir o roteamento para nossa aplicação. Você pode consultar GitHub para obter o código-fonte completo deste arquivo.

import { RouterModule } from '@angular/router';
import { ReactiveFormsModule } from  '@angular/forms';

@NgModule({
  ...    
  imports: [
    ...
    ReactiveFormsModule,
    RouterModule.forRoot([
      { path: '', component: ReactiveFormComponent },
      { path: 'reactive-form', component: ReactiveFormComponent }
    ]),
  ],
})

Demonstração da execução

Usaremos o comando abaixo para iniciar o servidor:

ng serve -o

Este comando iniciará a aplicação no seu navegador. Caso não abra automaticamente, você pode digitar http://localhost:4200/ na barra do seu navegador. Você pode realizar todas as validações de formulário que vimos aqui.

Esta aplicação também está hospedada em https://ng-forms-validation.herokuapp.com/. Navegue até o link e faça alguns testes para uma melhor compreensão.

Resumo

Criamos um modelo de formulário de registro de usuário utilizando a abordagem reactive-form no Angular. Também implementamos as validações integradas e personalizadas no formulário. Por fim, utilizamos a biblioteca do Bootstrap para a estilização.

Você pode obter o código-fonte no GitHub e fazer alguns testes para uma melhor compreensão.

Você pode encontrar outras publicações como esta no blog do autor, Ankit Sharma (em inglês).