Artigo original: https://www.freecodecamp.org/news/how-to-validate-angular-template-driven-forms/

Introdução

Neste artigo, aprenderemos sobre validações em formulários do Angular orientados a templates. Criaremos um formulário simples de registro de usuário e implementaremos algumas validações embutidas nele. Junto às validações embutidas, implementaremos algumas validações customizadas para o formulário orientado a templates.

Consideraremos as seguintes validações customizadas para essa demonstração:

  • Verificação de disponibilidade do nome de usuário;
  • Validação de padrão de senha;
  • Correspondência da senha inserida em dois campos diferentes.

Dê uma olhada na aplicação funcionando:

TemplateFormValidation

Pré-requisitos

  • Instale o Visual Studio Code aqui;
  • Instale a última versão da Angular CLI por aqui;
  • Instale a última versão LTS do Node.js aqui.

Código fonte

Você pode acessar o código fonte pelo GitHub.

Crie a aplicação do Angular

Navegue até o diretório no qual deseja criar o seu 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 aplicação do Angular. A opção para criar o módulo de roteamento é definida como false e a extensão de arquivos de estilização é definida como SCSS. Esse comando criará o projeto do Angular com o nome angular-forms-validation.

Dirija-se ao diretório do novo projeto e abra-o no VS Code usando o conjunto de comandos abaixo.

cd angular-forms-validation
code .

Instale o Bootstrap

Execute o seguinte comando para instalar o Bootstrap:

npm install bootstrap --save

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

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

Crie o serviço de validação

Execute o seguinte comando para criar um serviço:

ng g s services\customvalidation

Esse comando criará um subdiretório chamado services, que possui dois arquivos dentro dele – customvalidation.service.ts e customvalidation.service.spec.ts. Abra customvalidation.service.ts e insira o seguinte código dentro dele.

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 no seu formulário. O parâmetro para esse método é do tipo AbstractControl, que é uma classe base para oFormControl.

Usaremos uma expressão regular para validar a senha. Ela verificará as seguintes quatro condições na senha:

  • A senha deve ter, no mínimo, oito caracteres;
  • Deve possuir pelo menos uma letra minúscula;
  • Deve conter, no mínimo, uma letra maiúscula;
  • Deve ter, ao menos, um número.

Se a senha falhar na verificação da regex, definiremos a propriedade invalidPassword como sendo verdadeira (true).

O método MatchPassword é usado para comparar as senhas em dois campos. Esse método receberá dois parâmetros do tipo string, os quais representam os nomes dos campos a serem comparados. Utilizaremos o FormControl para esses dois campos e, em seguida, compararemos os valores contidos neles. Se os valores não corresponderem, definiremos a propriedade passwordMismatch como verdadeira (true).

O método userNameValidator é utilizado para verificar se o nome de usuário já foi utilizado ou não. Esse método receberá 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 (true).

Estamos utilizando a função setTimeout para invocar essa verificação a cada dois segundos. Isso assegurará que o erro seja disparado após dois segundos a partir do momento em que o usuário parar de digitar no campo.

Por questão de simplicidade deste artigo, estamos fazendo o uso de um array estático para buscar a disponibilidade de nomes de usuário. Idealmente, essa deveria ser uma chamada de serviço ao servidor para procurar pelo valor em um banco de dados.

Crie o modelo de usuário

Crie uma pasta chamada models dentro de src/app. Adicione um novo arquivo dentro do diretório models, chamado user.ts. Insira o seguinte código dentro do arquivo user.ts.  

export class User {
    public name: string;
    public email: string;
    public username: string;
    public password: string;
    public confirmPassword: string;
}

Crie diretivas customizadas

Criaremos diretivas customizadas para implementar validators customizados para o formulário orientado a templates.

Execute o comando exibido abaixo para criar a diretiva passwordPattern.

ng g d directives\passwordPattern

Este comando criará uma pasta chamada directives, que possui dois arquivos dentro de si – passwordPattern.directive.ts e passwordPattern.directive.spec.ts. Abra o passwordPattern.directive.ts e insira o seguinte código dentro dele.

import { Directive } from '@angular/core';
import { NG_VALIDATORS, Validator, AbstractControl } from '@angular/forms';
import { CustomvalidationService } from '../services/customvalidation.service';

@Directive({
  selector: '[appPasswordPattern]',
  providers: [{ provide: NG_VALIDATORS, useExisting: PasswordPatternDirective, multi: true }]
})
export class PasswordPatternDirective implements Validator {

  constructor(private customValidator: CustomvalidationService) { }

  validate(control: AbstractControl): { [key: string]: any } | null {
    return this.customValidator.patternValidator()(control);
  }
}

Essa diretiva é usada para validar o padrão da senha. Implementaremos a interface Validator na classe PasswordPatternDirective. Sobrescreveremos o método validate, o qual recebe um parâmetro do tipo AbstractControl, que é o controle que desejamos validar. Em seguida, invocaremos o método patternValidator do serviço.

Execute o comando mostrado abaixo para criar a diretiva matchPassword:

ng g d directives\matchPassword

Abra o arquivo matchPassword.directive.ts e insira o seguinte código dentro dele:

import { Directive, Input } from '@angular/core';
import { NG_VALIDATORS, Validator, ValidationErrors, FormGroup } from '@angular/forms';
import { CustomvalidationService } from '../services/customvalidation.service';

@Directive({
  selector: '[appMatchPassword]',
  providers: [{ provide: NG_VALIDATORS, useExisting: MatchPasswordDirective, multi: true }]
})
export class MatchPasswordDirective implements Validator {
  @Input('appMatchPassword') MatchPassword: string[] = [];

  constructor(private customValidator: CustomvalidationService) { }

  validate(formGroup: FormGroup): ValidationErrors {
    return this.customValidator.MatchPassword(this.MatchPassword[0], this.MatchPassword[1])(formGroup);
  }
}

Essa diretiva é usada para validar se as senhas inseridas nos dois campos correspondem ou não. Essa diretiva receberá uma entrada do tipo array de strings, o qual contém os campos a serem comparados. Sobrescreveremos o método validate e passaremos o parâmetro do tipo FormGroup. Em seguida, invocaremos o método MatchPassword do serviço.  

Execute o comando abaixo para criar a diretiva validateUserName:

ng g d directives\validateUserName

Abra o arquivo validateUserName.directive.ts e introduza o seguinte código nele:

import { Directive, forwardRef } from '@angular/core';
import { Validator, AbstractControl, NG_ASYNC_VALIDATORS } from '@angular/forms';
import { CustomvalidationService } from '../services/customvalidation.service';
import { Observable } from 'rxjs';

@Directive({
  selector: '[appValidateUserName]',
  providers: [{ provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => ValidateUserNameDirective), multi: true }]

})
export class ValidateUserNameDirective implements Validator {

  constructor(private customValidator: CustomvalidationService) { }

  validate(control: AbstractControl): Promise<{ [key: string]: any }> | Observable<{ [key: string]: any }> {
    return this.customValidator.userNameValidator(control);
  }
}

Essa diretiva é usada para validar a disponibilidade do nome de usuário. Sobrescreveremos o método validate e passaremos um parâmetro do tipo AbstractControl. Em seguida, invocaremos o método userNameValidator do serviço. Esse método retornará uma promise.

Crie o componente do formulário orientado a templates

Execute o comando apresentado abaixo para criar o componente do formulário orientado a templates:

ng g c template-driven-form

Abra o arquivo template-driven-form.component.ts e insira o seguinte código nele:

import { Component } from '@angular/core';
import { User } from '../models/user';

@Component({
  selector: 'app-template-driven-form',
  templateUrl: './template-driven-form.component.html',
  styleUrls: ['./template-driven-form.component.scss']
})
export class TemplateDrivenFormComponent {

  userModal = new User();

  constructor() { }

  onSubmit() {
    alert('Form Submitted succesfully!!!\n Check the values in browser console.');
    console.table(this.userModal);
  }
}

Criamos um objeto userModal do tipo User. Vincularemos os campos do formulário com a propriedade desse objeto. O método onSubmit mostrará a mensagem de sucesso na tela e exibirá os conteúdos do formulário no console.

Abra o arquivo template-driven-form.component.html e introduza o seguinte código nele:

<div class="container">
    <div class="row">
        <div class="col-md-8 mx-auto">
            <div class="card">
                <div class="card-header">
                    <h3>Angular Template-driven Form</h3>
                </div>
                <div class="card-body">
                    <form class="form" #registerForm="ngForm" [appMatchPassword]="['password', 'confirmPassword']"
                        (ngSubmit)="registerForm.form.valid && onSubmit()" novalidate>
                        <div class=" form-group">
                            <label>Name</label>
                            <input type="text" class="form-control" [(ngModel)]="userModal.name" name="name"
                                #name="ngModel" required>
                            <span class="text-danger"
                                *ngIf="(name.touched || registerForm.submitted) && name.errors?.required">
                                Name is required
                            </span>
                        </div>
                        <div class="form-group">
                            <label>Email</label>
                            <input type="text" class="form-control" [(ngModel)]="userModal.email" name="email"
                                #email="ngModel" required email>
                            <span class="text-danger"
                                *ngIf="(email.touched || registerForm.submitted) && email.errors?.required">
                                Email is required
                            </span>
                            <span class="text-danger" *ngIf="email.touched && email.errors?.email">
                                Enter a valid email address
                            </span>
                        </div>
                        <div class="form-group">
                            <label>User Name</label>
                            <input type="text" class="form-control" [(ngModel)]="userModal.username" name="username"
                                #username="ngModel" appValidateUserName required>
                            <span class="text-danger"
                                *ngIf="(username.touched || registerForm.submitted) && username.errors?.required">
                                User Name is required
                            </span>
                            <span class="text-danger" *ngIf="username.touched && username.errors?.userNameNotAvailable">
                                User Name not available
                            </span>
                        </div>
                        <div class="form-group">
                            <label>Password</label>
                            <input type="password" class="form-control" [(ngModel)]="userModal.password" name="password"
                                #password="ngModel" appPasswordPattern required>
                            <span class="text-danger"
                                *ngIf="(password.touched || registerForm.submitted) && password.errors?.required">
                                Password is required
                            </span>
                            <span class="text-danger" *ngIf="password.touched && 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" [(ngModel)]="userModal.confirmPassword"
                                name="confirmPassword" #confirmPassword="ngModel" required>
                            <span class="text-danger"
                                *ngIf="(confirmPassword.touched || registerForm.submitted) && confirmPassword.errors?.required">
                                Confirm Password is required
                            </span>
                            <span class="text-danger"
                                *ngIf="confirmPassword.touched && 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 formulário orientado a templates e usaremos o card do Bootstrap para estilização. O card-header conterá um título, enquanto o card-body terá os campos do formulário.

Usaremos a diretiva appMatchPassword em nosso formulário e passaremos para validação os campos de senha e confirmPassword. A propriedade ngModel é usada para vincular o controle do formulário ao modelo.

Para validar a disponibilidade do nome de usuário, usaremos a diretiva appValidateUserName no campo username. Do mesmo modo, utilizaremos a diretiva appPasswordPattern no campo de senha para validar o padrão da senha.

Verificaremos os erros nos controles do formulário e, então, exibiremos na tela a mensagem de erro de validação apropriada.

Crie um componente nav-bar

Execute o comando abaixo para criar um componente nav-bar:

ng g c nav-bar

Abra o arquivo nav-bar.component.html e insira nele o seguinte código:

<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]='["/template-form"]'>Template Form</a>
            </li>
        </ul>
    </div>
</nav>

Aqui, estamos adicionando o link de navegação para o componente de formulário orientado a templates.

Atualize o componente app

Abra o arquivo app.component.html e introduza nele o código a seguir:

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

Atualize o módulo de app

Importaremos o módulo de formulários e, também, configuraremos o roteamento para nossa aplicação no módulo do app. Adicione o seguinte código no arquivo app.module.ts. Você pode consultar o GitHub para obter o código fonte completo deste arquivo:

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

@NgModule({
  ...    
  imports: [
    ...
    FormsModule,
    RouterModule.forRoot([
      { path: '', component: TemplateDrivenFormComponent },
      { path: 'template-form', component: TemplateDrivenFormComponent }
    ]),
  ],
})

Execução de demonstração

Use o comando a seguir para iniciar o servidor da web:

ng serve -o

Este comando iniciará a aplicação em seu navegador padrão no http://localhost:4200/. Você pode realizar todas as validações de formulário que discutimos aqui.

Resumo

Criamos um formulário de registro de usuário de exemplo usando a abordagem de formulário orientado a templates no Angular. Implementamos no formulário as validações nativas, assim como as validações customizadas. A biblioteca do Bootstrap foi usada para estilização do formulário.

Acesse o código-fonte pelo GitHub e explore o repositório para ter um melhor entendimento.

Veja também (recursos em inglês)

Você pode encontrar este artigo em inglês, Template-Driven Form Validation In Angular, e outros artigos semelhantes no blog do autor.