Atualizado: 03/01/2025

Reactive Forms - Angular

Nos temas anteriores, foi abordado o método Template-Driven, que se concentra no template do componente. Para trabalhar com o formulário e seus elementos, eram aplicadas as diretivas NgModel e NgForm aos elementos HTML no template do componente, e as regras de validação eram definidas diretamente nas tags dos elementos utilizando atributos como required e pattern. No entanto, existe uma abordagem alternativa: o uso de Reactive Forms. Vamos entender do que se trata.

Na abordagem Reactive Forms, é criado um conjunto de objetos FormGroup e FormControl para o formulário. O próprio formulário e suas subseções são representados pela classe FormGroup, enquanto os elementos individuais de entrada são representados pela classe FormControl. Um exemplo básico de criação de formulário seria:

myForm : FormGroup = new FormGroup();

Agora, vamos adicionar alguns elementos ao formulário:

myForm : FormGroup = new FormGroup({
  "userName": new FormControl(),
  "userEmail": new FormControl(),
  "userPhone": new FormControl()
});

Aqui, foram definidos três elementos: userName, userEmail e userPhone.

O objeto FormControl pode ser configurado de várias formas (mais detalhes podem ser encontrados na documentação oficial FormControl). Em particular, é possível passar um valor padrão como primeiro parâmetro, e um conjunto de validadores como segundo parâmetro:

myForm : FormGroup = new FormGroup({
  "userName": new FormControl("Tom", Validators.required),
  "userEmail": new FormControl("", [
      Validators.required, 
      Validators.email
  ]),
  "userPhone": new FormControl("", Validators.pattern("[0-9]{10}")) 
});

Aqui, uma série de validadores foi aplicada aos elementos. O validador Validators.required exige que o campo tenha um valor preenchido. O Validators.email verifica se a string inserida é um endereço de e-mail válido. O validador Validators.pattern("[0-9]{10}") verifica se o valor inserido corresponde ao padrão especificado pela expressão regular. Todos os validadores internos podem ser encontrados na documentação oficial Validators. Caso haja vários validadores, eles são passados como um array.

Para vincular o objeto myForm a um elemento específico do formulário, utilizamos o atributo formGroup:

<form [formGroup]="myForm">

Além disso, é necessário conectar os objetos FormControl aos elementos de entrada utilizando o atributo formControlName:

<input name="name" formControlName="userName" />

Esse elemento será associado ao objeto userName: new FormControl("Tom").

Agora, vamos ver como esses objetos interagem com o template do componente. Para isso, definimos o seguinte componente:

import { Component } from "@angular/core";
import { FormsModule, FormGroup, FormControl, Validators, ReactiveFormsModule } from "@angular/forms";

@Component({
    selector: "my-app",
    standalone: true,
    imports: [FormsModule, ReactiveFormsModule],
    styles: ` 
        div {margin: 5px 0;}
        .alert {color:red;}
        input.ng-touched.ng-invalid {border:solid red 2px;}
        input.ng-touched.ng-valid {border:solid green 2px;}
    `,
    template: `<form [formGroup]="myForm" novalidate (ngSubmit)="submit()">
                    <div>
                        <label>Nome</label><br>
                        <input name="name" formControlName="userName" />
  
                        @if(myForm.controls["userName"].invalid && myForm.controls["userName"].touched){
                            <div class="alert">Nome incorreto</div>
                        }
                    </div>
                    <div>
                        <label>Email</label><br>
                        <input name="email" formControlName="userEmail" />
  
                        @if(myForm.controls["userEmail"].invalid && myForm.controls["userEmail"].touched){
                            <div class="alert">Email incorreto</div>
                        }
                    </div>
                    <div>
                        <label>Telefone</label><br>
                        <input name="phone" formControlName="userPhone" />
  
                        @if(myForm.controls["userPhone"].invalid && myForm.controls["userPhone"].touched){
                            <div class="alert">Número de telefone incorreto</div>
                        }
                    </div>
                    <button [disabled]="myForm.invalid">Enviar</button>
                </form>`
})
export class AppComponent { 
    myForm: FormGroup;

    constructor() {
        this.myForm = new FormGroup({
            "userName": new FormControl("Tom", Validators.required),
            "userEmail": new FormControl("", [
                Validators.required, 
                Validators.email 
            ]),
            "userPhone": new FormControl("", Validators.pattern("[0-9]{11}")) 
        });
    }

    submit() {
        console.log(this.myForm);
    }
}

Para exibir erros de validação, utilizamos blocos div, nos quais definimos expressões do tipo:

*ngIf="myForm.controls['userName'].invalid && myForm.controls['userName'].touched"

Usando expressões como myForm.controls['userName'], podemos acessar o elemento desejado do formulário e obter seu estado ou valor. Neste caso, se o valor do campo for inválido e o campo já tiver recebido foco, uma mensagem de erro será exibida.

Para que isso funcione corretamente, é necessário importar o módulo ReactiveFormsModule:

import { ReactiveFormsModule } from "@angular/forms";
Reactive Forms em Angular

Definindo Validadores Personalizados

Além dos validadores internos, também podemos definir nossos próprios validadores. Por exemplo, vamos definir um validador personalizado dentro da classe do componente:

export class AppComponent { 
  myForm: FormGroup;

  constructor() {
      this.myForm = new FormGroup({
          "userName": new FormControl("Tom", [Validators.required, this.userNameValidator]),
          "userEmail": new FormControl("", [
              Validators.required, 
              Validators.email
          ]),
          "userPhone": new FormControl()
      });
  }

  submit() {
      console.log(this.myForm);
  }

  // Validador personalizado
  userNameValidator(control: FormControl): { [s: string]: boolean } | null {
      if (control.value === "admin") {
          return { "userName": true };
      }
      return null;
  }
}

Essencialmente, um validador personalizado é um método comum. Nesse caso, é o método userNameValidator. Ele recebe como parâmetro o controle de formulário ao qual o validador é aplicado e retorna um objeto onde a chave é uma string e o valor é true se a validação falhar.

No exemplo acima, o validador verifica se o valor do campo é igual à string "admin". Se for, ele retorna o objeto { "userName": true }, indicando que o campo não passou na validação. Caso contrário, retorna null, o que significa que o campo passou na validação.

Esse validador é então adicionado ao controle userName:

"userName": new FormControl("Tom", [Validators.required, this.userNameValidator])

Se o valor inserido no campo de nome for "admin", o campo falhará na validação, e o formulário refletirá esse erro. A personalização de validadores é muito útil quando as validações internas não cobrem casos específicos.

Essa flexibilidade nos permite construir formulários que atendem às necessidades específicas de cada aplicação, garantindo que todas as validações sejam aplicadas de forma correta.

Criando Validadores em Angular

Arrays de Elementos e FormArray

Alguns elementos do formulário podem se referir à mesma característica. Por exemplo, um usuário pode ser solicitado a fornecer os números de telefone que ele possui. Pode haver vários números, mas todos representariam a mesma característica: "números de telefone". Sendo assim, faz sentido agrupar todos os campos de entrada de números de telefone em um array. No Angular, podemos implementar facilmente essa funcionalidade usando a classe FormArray.

Vamos modificar o código do componente AppComponent da seguinte forma:

import { Component } from "@angular/core";
import { FormsModule, ReactiveFormsModule, FormGroup, FormControl, Validators, FormArray } from "@angular/forms";

@Component({
    selector: "my-app",
    standalone: true,
    imports: [FormsModule, ReactiveFormsModule],
    styles: ` 
        div {margin: 5px 0;}
        .alert {color:red;}
        input.ng-touched.ng-invalid {border:solid red 2px;}
        input.ng-touched.ng-valid {border:solid green 2px;}
    `,
    template: `<form [formGroup]="myForm" novalidate (ngSubmit)="submit()">
                    <div>
                        <label>Nome</label><br>
                        <input name="name" formControlName="userName" />
  
                        @if(myForm.controls["userName"].invalid && myForm.controls["userName"].touched){
                            <div class="alert">Nome incorreto</div>
                        }
                    </div>
                    <div>
                        <label>Email</label><br>
                        <input name="email" formControlName="userEmail" />
  
                        @if(myForm.controls["userEmail"].invalid && myForm.controls["userEmail"].touched){
                            <div class="alert">Email incorreto</div>
                        }
                    </div>
                    <div formArrayName="phones">
                        @for(phone of getFormsControls()["controls"]; track $index){
                            <div>
                                <label>Telefone</label><br>
                                <input formControlName="{{$index}}" />
                            </div>
                        }
                    </div>
                    <button (click)="addPhone()">Adicionar telefone</button>
                    <button [disabled]="myForm.invalid">Enviar</button>
                </form>`
})
export class AppComponent { 
    myForm: FormGroup;

    constructor() {
        this.myForm = new FormGroup({
            "userName": new FormControl("Tom", [Validators.required]),
            "userEmail": new FormControl("", [
                Validators.required, 
                Validators.email
            ]),
            "phones": new FormArray([
                new FormControl("+7", Validators.required)
            ])
        });
    }

    getFormsControls(): FormArray {
        return this.myForm.controls["phones"] as FormArray;
    }

    addPhone() {
        (<FormArray>this.myForm.controls["phones"]).push(new FormControl("+7", Validators.required));
    }

    submit() {
        console.log(this.myForm);
    }
}

Agora, os campos de entrada para números de telefone são representados como um array:

"phones": new FormArray([
    new FormControl("+7", Validators.required)
])

O FormArray armazena um conjunto de objetos FormControl. Neste caso, apenas um objeto foi adicionado inicialmente.

Para permitir a adição dinâmica de novos controles de entrada, o método addPhone() foi implementado no componente:

addPhone() {
  (<FormArray>this.myForm.controls["phones"]).push(new FormControl("+7", Validators.required));
}

Nesta função, primeiro obtemos o array de controles usando this.myForm.controls["phones"], convertemos para FormArray e, em seguida, adicionamos um novo controle ao array com o método push.

Para simplificar o acesso aos controles do array, foi criado o método getFormsControls(), que retorna o objeto FormArray:

getFormsControls(): FormArray {
    return this.myForm.controls["phones"] as FormArray;
}

No código HTML, os objetos do FormArray são renderizados no formulário usando a estrutura @for:

<div formArrayName="phones">
@for(phone of getFormsControls()["controls"]; track $index) {
    <div>
        <label>Telefone</label><br>
        <input formControlName="{{$index}}" />
    </div>
}
</div>

O container de todos os elementos de entrada é identificado pela diretiva formArrayName="phones", enquanto cada elemento recebe como nome o índice atual, através de formControlName="{{ $index }}".

Por fim, o botão "Adicionar telefone" permite adicionar um novo campo de entrada para inserir outro número de telefone ao formulário:

<button (click)="addPhone()">Adicionar telefone</button>

O exemplo acima demonstra como criar um formulário com um array de elementos de entrada. Essa abordagem é muito útil quando precisamos coletar várias entradas de um mesmo tipo.

FormArray in Angular

FormBuilder

A classe FormBuilder oferece uma abordagem alternativa para a criação de formulários no Angular, tornando o processo mais simplificado e eficiente.

Aqui está um exemplo de como usar o FormBuilder:

import { Component } from "@angular/core";
import { FormsModule, ReactiveFormsModule, FormGroup, 
        FormControl, Validators, FormArray, FormBuilder } from "@angular/forms";

@Component({
    selector: "my-app",
    standalone: true,
    imports: [FormsModule, ReactiveFormsModule],
    styles: ` 
        div {margin: 5px 0;}
        .alert {color:red;}
        input.ng-touched.ng-invalid {border:solid red 2px;}
        input.ng-touched.ng-valid {border:solid green 2px;}
    `,
    template: `<form [formGroup]="myForm" novalidate (ngSubmit)="submit()">
                    <div>
                        <label>Nome</label><br>
                        <input name="name" formControlName="userName" />
  
                        @if(myForm.controls["userName"].invalid && myForm.controls["userName"].touched){
                            <div class="alert">Nome incorreto</div>
                        }
                    </div>
                    <div>
                        <label>Email</label><br>
                        <input name="email" formControlName="userEmail" />
  
                        @if(myForm.controls["userEmail"].invalid && myForm.controls["userEmail"].touched){
                            <div class="alert">Email incorreto</div>
                        }
                    </div>
                    <div formArrayName="phones">
                        @for(phone of getFormsControls()["controls"]; track $index){
                            <div>
                                <label>Telefone</label><br>
                                <input formControlName="{{$index}}" />
                            </div>
                        }
                    </div>
                    <button (click)="addPhone()">Adicionar telefone</button>
                    <button [disabled]="myForm.invalid">Enviar</button>
                </form>`
})
export class AppComponent { 
    myForm: FormGroup;

    constructor(private formBuilder: FormBuilder) {
        this.myForm = formBuilder.group({
            "userName": ["Tom", [Validators.required]],
            "userEmail": ["", [Validators.required, Validators.email]],
            "phones": formBuilder.array([
                ["+7", Validators.required]
            ])
        });
    }

    getFormsControls(): FormArray {
        return this.myForm.controls["phones"] as FormArray;
    }

    addPhone() {
        (<FormArray>this.myForm.controls["phones"]).push(new FormControl("+7", Validators.required));
    }

    submit() {
        console.log(this.myForm);
    }
}

O FormBuilder é injetado no construtor como um serviço. Ele fornece métodos convenientes como group() e array() para criar um objeto FormGroup ou FormArray.

O resultado final do componente é o mesmo que o exemplo anterior.

Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com