No internet connection
  1. Home
  2. JavaScript

Forms em Angular

Formulários são uma parte essencial no desenvolvimento web, e o Angular acertou em cheio na forma como se trabalha formulários. O Angular oferece duas abordagens principais para trabalhar com formulários: Template-driven Forms (Formulários Orientados por Template) e Reactive Forms (Formulários Reativos). Ambas possuem suas vantagens e são úteis em diferentes cenários.

Template-driven Forms:

Características:

  • São mais simples de usar e mais adequados para formulários simples.
  • A lógica é principalmente no template HTML.
  • O estado e a validação dos inputs são gerenciados automaticamente pelo Angular.

O Template-driven Forms vem do módulo FormsModule então não pode esquecer de importa-lo no módulo que seu componente pertence, ou importar no componente se ele for standalone.

Exemplo Básico:

<form #myForm="ngForm" (ngSubmit)="onSubmit()">
  <input type="text" name="username" ngModel required>
  <input type="password" name="password" ngModel required minlength="6">
  <button type="submit">Submit</button>
</form>

No componente:

@Component({
  // ...
})
export class MyComponent {
  onSubmit(form: NgForm) {
    if (form.valid) {
      console.log(form.value);
      // Lógica para enviar os dados do formulário
    }
  }
}

Outra forma de usar o Template-driven Forms é através do conceito de Two-Way Binding (em resumo é um dado que pode ser alterado tanto pelo template quanto pela classe) através do [(ngModel)] o funcionamento é simples:

<input type="text" [(ngModel)]="firstName">
<p>O valor do campo é: {{ firstName }}</p>
export class MyComponent {
  firstName: string = ''; // O valor inicial do campo

  // Outras lógicas do componente...
}

Reactive Forms

Características

  • Mais poderosos e escaláveis, ideais para formulários complexos.
  • A lógica reside no componente TypeScript.
  • Oferecem maior controle sobre a validação, estados e reatividade dos formulários.

O Reactive Forms será explicado usando o FormBuilder mas também existe a criação de formulários reativos usando o FormGroup diretamente, tem apenas algumas diferenças sutis, como não precisar injeta-lo como dependência.

O formulário reativo vem do módulo ReactiveFormsModule então não pode esquecer de importa-lo no módulo que seu componente pertence, ou importar no componente se ele for standalone.

FormBuilder

FormBuilder é uma forma de criar formulários mais complexos e mantê-lo organizado. Para entender como fazer um formulário com ele, vamos criar um formulário de inscrição de um evento.

Primeiramente, o FormBuilder é um serviço que precisa ser injetado no componente que o usará. Para criar um formulário, usamos o método FormBuilder.group que aguarda um objeto com o nome dos campos e cada campo será um array onde a primeira posição é o valor inicial e a segunda posição é opcional, que seria o lugar para inserir um validador ou um array de validadores. Com o exemplo será mais fácil de entender.

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';

@Component({
  selector: 'app-event-registration',
	standalone: true,
  imports: [ReactiveFormsModule],
  templateUrl: './event-registration.component.html',
  styleUrls: ['./event-registration.component.css']
})
export class EventRegistrationComponent {
  registrationForm: FormGroup;

  constructor(private formBuilder: FormBuilder) {
    this.registrationForm = this.formBuilder.group({
      firstName: ['', Validators.required],
      lastName: ['', Validators.required],
      email: ['', [Validators.required, Validators.email]],
      phone: ['', Validators.required]
    });
  }

  onSubmit() {
    if (this.registrationForm.valid) {
      console.log(this.registrationForm.value);
      // Lógica para enviar os dados do formulário
    }
  }
}

Perceba que o tipo do formulário é FormGroup, que fornece vários métodos para trabalhar com o formulário, como é o exemplo do valid que verifica se todos os campos passaram nos validadores e o value que contém um objeto com o valor de todos os campos. Uma lista de algum dos métodos que o FormGroup possui:

controls é uma propriedade que retorna um objeto que contém todos os controles individuais (campos) do FormGroup.
setValue() define os valores para todos os campos no FormGroup. Você deve passar um objeto com chaves correspondentes aos nomes dos campos e os valores que deseja atribuir a eles.
patchValue() semelhante ao setValue(), mas permite definir valores apenas para os campos especificados, mantendo os valores atuais para os campos não especificados.
reset() redefine o estado do formulário para os valores iniciais.
value é uma propriedade que retorna um objeto contendo os valores atuais de todos os campos no FormGroup.
valid é uma propriedade somente leitura que indica se o FormGroup é válido (ou seja, se todos os campos passaram nas validações).
errors é uma propriedade que retorna um objeto contendo erros de validação atuais no FormGroup. Se o formulário estiver válido, será null.
dirty é uma propriedade somente leitura que indica se o usuário modificou algum dos campos no FormGroup.
touched é uma propriedade somente leitura que indica se o usuário clicou em algum dos campos no FormGroup.
get() é usado para resgatar as propriedades de um elemento do formulário. Usado geralmente para verificar erros.

*Para ver todos os métodos do FormGroup acesse a documentação oficial.

Agora para relacionar os campos do FormBuilder com o form e inputs no HTML , usamos a diretiva [formGroup] para relacionar o form ao registrationForm e nos inputs usamos o atributo formControlName para relacionar cada campo individual, veja no exemplo como fica.

<form [formGroup]="registrationForm" (ngSubmit)="onSubmit()">
  <div>
    <label for="firstName">Nome:</label>
    <input type="text" id="firstName" formControlName="firstName">
  </div>
  <div>
    <label for="lastName">Sobrenome:</label>
    <input type="text" id="lastName" formControlName="lastName">
  </div>
  <div>
    <label for="email">Email:</label>
    <input type="email" id="email" formControlName="email">
  </div>
  <div>
    <label for="phone">Telefone:</label>
    <input type="tel" id="phone" formControlName="phone">
  </div>
  <button type="submit" [disabled]="!registrationForm.valid">Enviar</button>
</form>

Veja o que retorna do console.log(this.registrationForm.value) no onSubmit:

Validadores

Os validadores verificam são funções utilizadas para verificar se um campo de formulário atende a determinados critérios de validação. Existem validadores básicos já criados dentro do Angular mas principalmente é possível criar validadores personalizados.

Validadores Padrão:

  • Validators.required: Verifica se o campo é obrigatório.
  • Validators.minLength: Define um comprimento mínimo para uma string.
  • Validators.maxLength: Define um comprimento máximo para uma string.
  • Validators.pattern: Verifica se o valor corresponde a um padrão de expressão regular.
  • Validators.email: Verifica se o valor é um endereço de e-mail válido.

Criando Validadores

Para criar um validador personalizado, basta criar uma função que retorne outra função do tipo ValidatorFn.

Vamos criar um exemplo de validador que verifica se a entrada é um número par:

import { AbstractControl, ValidatorFn, Validators } from '@angular/forms';

function evenNumberValidator(): ValidatorFn {
  return (control: AbstractControl): { [key: string]: any } | null => {
    const value = control.value;
    if (value % 2 !== 0) {
      return { 'evenNumber': { value } };
    }
    return null;
  };
}

A função evenNumberValidator retorna um ValidatorFn, que é uma função que recebe um AbstractControl, que seria um tipo de dado parecido com o FormGroup para aquele campo do formulário, e retorna um objeto com o nome do erro e a mensagem desse erro ou qualquer outra coisa se a validação falhar ou null se a validação for bem-sucedida.

Para usar esse validador em um campo do formulário, você pode passar essa função ao criar o controle, como no exemplo a seguir:

import { Component } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

@Component({
  selector: 'app-my-component',
	standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="myForm">
      <input type="number" formControlName="numberField">
      <div *ngIf="myForm.get('numberField').hasError('evenNumber')">
        Por favor, insira um número par.
      </div>
    </form>
  `
})
export class MyComponent {
  myForm: FormGroup;

  constructor(private formBuilder: FormBuilder) {
    this.myForm = this.formBuilder.group({
      numberField: ['', evenNumberValidator()]
    });
  }
}

Neste exemplo, evenNumberValidator() é utilizado para validar o campo numberField do formulário. Quando o valor inserido não é um número par, o erro evenNumber é ativado e pode ser verificado no template para exibir uma mensagem de erro ou qualquer outro feedback visual necessário.

Aqui está outro exemplo de um validator para verificar se a senha inserida é forte:

import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';

export function strongPasswordValidator(): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const value = control.value;

    if (!value) return null;

    const hasUppercase = /[A-Z]/.test(value);
    const hasLowercase = /[a-z]/.test(value);
    const hasNumber = /\d/.test(value);
    const hasSymbol = /[$@#!&+-]/.test(value);
    const hasInvalidChar = /[^a-zA-Z0-9$@#!&+-]/.test(value);

    const errors: ValidationErrors = {};

    if (!hasUppercase) errors['uppercase'] = true;
    if (!hasLowercase) errors['lowercase'] = true;
    if (!hasNumber) errors['number'] = true;
    if (!hasSymbol) errors['symbol'] = true;
    if (hasInvalidChar) errors['invalidChar'] = true;

    //Verificando se tem alguma propriedade em errors
    return Object.keys(errors).length ? errors : null;
  };
}

Referências

  • 0 respostas