Experts in Angular

FormsFormulários Reativos
Formulários Reativos como Instrumentos de Poder

Formulários Reativos

Imagine seus formulários como entidades vivas, com dados fluindo e se transformando em tempo real.
Os formulários reativos do Angular oferecem um controle preciso sobre esse fluxo, permitindo que você modele, manipule e valide dados de forma elegante e eficiente.

Este guia mostra como criar e atualizar um controle de formulário básico, progredir para o uso de vários controles em um grupo, validar valores do formulário e criar formulários dinâmicos onde você pode adicionar ou remover controles em tempo de execução.

Visão Geral dos Formulários Reativos: O Poder do Modelo Imutável

Ao contrário dos formulários tradicionais, onde os dados são mutáveis e as mudanças podem ser imprevisíveis, os formulários reativos adotam uma abordagem explícita e imutável. Cada interação do usuário gera um novo estado do formulário, preservando a integridade dos dados a cada passo.

No coração dos formulários reativos estão os observables, fluxos contínuos de valores que representam as entradas e o estado do formulário. Essa abordagem síncrona garante que você tenha acesso instantâneo e consistente aos dados, facilitando a manipulação e a validação em tempo real.

Comparando com Formulários Template-Driven: A Escolha da Ferramenta Certa

Enquanto os formulários template-driven oferecem uma abordagem mais simples e direta, baseada em diretivas e dados mutáveis, os formulários reativos se destacam pela sua robustez, escalabilidade e facilidade de teste. A escolha entre as duas abordagens depende da complexidade do seu projeto e das suas preferências de desenvolvimento.

Vantagens dos Formulários Reativos:

  • Acesso síncrono ao modelo de dados: Manipule os dados do formulário de forma imediata e consistente, sem se preocupar com atrasos ou inconsistências.
  • Imutabilidade com operadores de observables: Transforme e manipule os dados do formulário de forma segura e previsível, utilizando operadores poderosos como map, filter e debounceTime.
  • Rastreamento de alterações através de fluxos de observables: Monitore as mudanças no formulário em tempo real, facilitando a validação e a atualização da interface do usuário.
  • Facilidade de teste: A natureza previsível dos formulários reativos simplifica a criação de testes unitários e de integração, garantindo a qualidade do seu código.

Desvendando os Segredos dos Formulários Reativos

Prepare-se para uma jornada emocionante pelo mundo dos formulários reativos em Angular. Nos próximos capítulos, exploraremos os blocos de construção essenciais, como FormControl, FormGroup e FormBuilder, e aprenderemos a criar formulários dinâmicos e validados, capazes de lidar com as mais diversas necessidades da sua aplicação.

Adicionando um Controle de Formulário Básico: Os Primeiros Passos para a Maestria

Dominar os formulários reativos começa com a compreensão de como adicionar e manipular controles individuais. Cada controle, como um campo de entrada de texto, é representado por uma instância FormControl, a unidade fundamental dos formulários reativos.

Gerando um Novo Componente e Criando um FormControl: Dando Vida ao Seu Controle

Utilize o Angular CLI para gerar um novo componente que irá abrigar o seu controle de formulário. Em seguida, dentro da classe do componente, instancie um novo FormControl e defina seu valor inicial.

ng generate component components/name-editor --standalone

Em seguida, dentro da classe do componente, instancie um novo FormControl e defina seu valor inicial.

import { Component } from '@angular/core';
import {FormControl, ReactiveFormsModule} from "@angular/forms";

@Component({
  selector: 'app-name-editor',
  standalone: true,
  imports: [
    ReactiveFormsModule
  ],
  template: `
      <label for="name">Name: </label>
      <input id="name" type="text" [formControl]="name">
  `,
  styles: ``
})
export class NameEditorComponent {
  name = new FormControl('');
}

Importando o Módulo ReactiveFormsModule: A Chave para o Reino Reativo

Observe que importamos o módulo ReactiveFormsModule do pacote @angular/forms.
Esse módulo contém as diretivas essenciais para o funcionamento dos formulários reativos, como formControl, que conecta os elementos do formulário ao modelo de dados.

Registrando o Controle no Template: Conectando o Modelo à Visão

Conectamos o controle criado no componente ao elemento de formulário no template. Utilizamos a diretiva formControl para estabelecer essa ligação.

Usando a sintaxe de vinculação no template, o controle de formulário agora está registrado no elemento de entrada name no template. O controle de formulário e o elemento DOM se comunicam entre si: a visualização reflete as mudanças no modelo, e o modelo reflete as mudanças na visualização.

<label for="name">Nome: </label>
<input id="name" type="text" [formControl]="name">

Exibindo o Componente

O FormControl atribuído à propriedade name é exibido quando o componente <app-name-editor> é adicionado a um template.

Exibindo o Componente

O FormControl atribuído à propriedade name é exibido quando o componente <app-name-editor> é adicionado a um template.

src\app\app.component.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import {NameEditorComponent} from "./components/app-name-editor/name-editor.component";

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, NameEditorComponent],
  template: `
    <h1>Welcome to {{title}}!</h1>
    <app-name-editor></app-name-editor>

  `,
  styles: [],
})
export class AppComponent {
  title = 'reactive-forms-module';
}

Exibindo um Valor de Controle de Formulário

Você pode exibir o valor das seguintes maneiras:

  • Através do observável valueChanges, onde você pode ouvir mudanças no valor do formulário no template usando AsyncPipe ou na classe do componente usando o método subscribe().
  • Com a propriedade value, que fornece um instantâneo do valor atual.

O exemplo a seguir mostra como exibir o valor atual usando interpolação no template.

<p>Valor: {{ name.value }}</p>

O valor exibido muda à medida que você atualiza o elemento de controle do formulário.

Os formulários reativos fornecem acesso a informações sobre um determinado controle por meio de propriedades e métodos fornecidos com cada instância. Essas propriedades e métodos da classe subjacente AbstractControl são usados para controlar o estado do formulário e determinar quando exibir mensagens ao lidar com validação de entrada.

Leia sobre outras propriedades e métodos do FormControl na Referência da API.

Substituindo um Valor de Controle de Formulário

Os formulários reativos têm métodos para alterar o valor de um controle programaticamente, o que lhe dá flexibilidade para atualizar o valor sem a interação do usuário.
Uma instância de controle de formulário fornece um método setValue() que atualiza o valor do controle de formulário e valida a estrutura do valor fornecido em relação à estrutura do controle.
Por exemplo, ao recuperar dados do formulário de uma API de backend ou serviço, use o método setValue() para atualizar o controle para seu novo valor, substituindo completamente o valor antigo.

O exemplo a seguir adiciona um método à classe do componente para atualizar o valor do controle para Deadpool usando o método setValue().

updateName() {
    this.name.setValue('Deadpool');
  }

Atualize o template com um botão para simular uma atualização de nome.
Quando você clica no botão Update Name, o valor inserido no elemento de controle do formulário é refletido como seu valor atual.

<label for="name">Nome: </label>
<input id="name" type="text" [formControl]="name">
<p>Valor: {{ name.value }}</p>
<button type="button" (click)="updateName()">Update Name</button>

O modelo de formulário é a fonte da verdade para o controle, então quando você clica no botão, o valor da entrada é alterado dentro da classe do componente, substituindo seu valor atual.

Dica Útil

Neste exemplo, você está usando um controle único. Ao usar o método setValue() com uma instância de grupo de formulário (form group) ou array de formulário (form array), o valor precisa corresponder à estrutura do grupo ou array.

Essa seção mostrou como adicionar e gerenciar um controle de formulário básico usando formulários reativos no Angular.
Nos próximos tópicos, exploraremos como agrupar controles de formulário, validar entradas e criar formulários dinâmicos para aplicações mais complexas.

Agrupando Controles de Formulário (Form Groups)

Em formulários, é comum ter controles relacionados. Formulários reativos no Angular oferecem duas formas de agrupar controles:

  • Form Groups: Define um formulário com um conjunto fixo de controles gerenciados juntos. É o que veremos aqui.
  • Form Arrays: Define um formulário dinâmico, onde você pode adicionar e remover controles em tempo de execução.

Assim como uma instância de controle de formulário oferece controle sobre um único campo de entrada, uma instância de grupo de formulário rastreia o estado do formulário de um grupo de instâncias de controle de formulário (por exemplo, um formulário).
Cada controle em uma instância de grupo de formulário é rastreado por nome ao criar o grupo de formulário. O exemplo a seguir mostra como gerenciar múltiplas instâncias de controle de formulário em um único grupo.

Gerar o Componente ProfileEditor

Para começar, vamos gerar um novo componente chamado ProfileEditor e importar as classes FormGroup e FormControl do pacote @angular/forms.

Comando:

ng generate component components/ProfileEditor --standalone
import { Component } from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule} from "@angular/forms";

@Component({
  selector: 'app-profile-editor',
  standalone: true,
  imports: [
    ReactiveFormsModule
  ],
  template: `
    <form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
      <label for="first-name">First Name: </label>
      <input id="first-name" type="text" formControlName="firstName">

      <label for="last-name">Last Name: </label>
      <input id="last-name" type="text" formControlName="lastName">

      <p>Complete o formulário para habilitar o botão.</p>
      <button type="submit" [disabled]="!profileForm.valid">Submit</button>
    </form>2,80
  `,
  styles: ``
})
export class ProfileEditorComponent {
  profileForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl('')
  });
  onSubmit() {
    // TODO: Use EventEmitter with form value
    console.warn(this.profileForm.value);
  }
}

Exibir o Componente

Para exibir o componente ProfileEditor que contém o formulário, adicione-o ao template principal da aplicação.

Código:

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import {NameEditorComponent} from "./components/app-name-editor/name-editor.component";
import {ProfileEditorComponent} from "./components/profile-editor/profile-editor.component";

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, NameEditorComponent, ProfileEditorComponent],
  template: `
    <h1>Welcome to {{title}}!</h1>
    <app-profile-editor></app-profile-editor>

  `,
  styles: [],
})
export class AppComponent {
  title = 'reactive-forms-module';
}

Criando Grupos de Formulários Aninhados

Os grupos de formulários podem aceitar tanto instâncias de controle de formulário individuais quanto outras instâncias de grupo de formulário como filhos. Isso torna a composição de modelos de formulário complexos mais fácil de manter e agrupar logicamente.

Para criar um grupo aninhado em profileForm, adicione um elemento address aninhado à instância do grupo de formulário.

Código:

profileForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
    address: new FormGroup({
      street: new FormControl(''),
      city: new FormControl(''),
      state: new FormControl(''),
      zip: new FormControl('')
    })
  });

Template do Grupo de Formulário Aninhado

Atualize o template para conectar a instância do grupo de formulário e seus elementos de entrada. Adicione o grupo de formulário address contendo os campos street, city, state e zip ao template do ProfileEditor.

<!-- src/app/profile-editor/profile-editor.component.html -->

<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
  <label for="first-name">First Name: </label>
  <input id="first-name" type="text" formControlName="firstName">

  <label for="last-name">Last Name: </label>
  <input id="last-name" type="text" formControlName="lastName">

  <div formGroupName="address">
    <h2>Address</h2>
    <label for="street">Street: </label>
    <input id="street" type="text" formControlName="street">

    <label for="city">City: </label>
    <input id="city" type="text" formControlName="city">

    <label for="state">State: </label>
    <input id="state" type="text" formControlName="state">

    <label for="zip">Zip Code: </label>
    <input id="zip" type="text" formControlName="zip">
  </div>

  <p>Complete o formulário para habilitar o botão.</p>
  <button type="submit" [disabled]="!profileForm.valid">Submit</button>
</form>

O formulário ProfileEditor é exibido como um grupo, mas o modelo é subdividido para representar áreas de agrupamento lógico.

Atualizando Partes do Modelo de Dados

Ao lidar com formulários complexos com múltiplos controles, muitas vezes você precisará atualizar apenas partes específicas do modelo de dados.
O Angular oferece duas maneiras de fazer isso:

MétodoDetalhes
setValue()Substitui o valor inteiro do controle ou grupo, requerendo um objeto completo.
patchValue()Atualiza apenas as propriedades especificadas no objeto, ignorando as demais.

setValue():

  • Uso: Define um novo valor para um controle individual ou para o grupo inteiro.
  • Estrito: Requer que você forneça um objeto que corresponda exatamente à estrutura do seu FormGroup. Se faltar alguma propriedade ou a estrutura estiver incorreta, um erro será lançado.
  • Exemplo:
this.profileForm.setValue({
  firstName: 'João',
  lastName: 'Silva',
  address: {
    street: 'Rua Principal, 123',
    city: 'São Paulo',
    state: 'SP',
    zip: '01001-000'
  }
});

patchValue():

  • Uso: Atualiza apenas as propriedades especificadas no objeto que você fornece, ignorando as outras.
  • Flexível: Não exige que você forneça um objeto completo, apenas as propriedades que deseja modificar.
  • Exemplo:
this.profileForm.patchValue({
  firstName: 'Maria',
  address: {
    street: 'Avenida Secundária, 456'
  }
});

Quando Usar Cada Método

  • setValue(): Ideal quando você precisa redefinir todo o formulário ou um grupo aninhado para um novo estado inicial.
  • patchValue(): Útil para atualizar partes específicas do formulário, como quando o usuário edita apenas alguns campos.

No ProfileEditorComponent, use o método updateProfile com o seguinte exemplo para atualizar o primeiro nome e o endereço do usuário.

 updateProfile() {
    this.profileForm.patchValue({
      firstName: 'Nancy',
      address: {
        street: '123 Drew Street'
      }
    });
  }

Simule uma atualização adicionando um botão ao template para atualizar o perfil do usuário sob demanda.

<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
  <label for="first-name">First Name: </label>
  <input id="first-name" type="text" formControlName="firstName">

  <label for="last-name">Last Name: </label>
  <input id="last-name" type="text" formControlName="lastName">

  <div formGroupName="address">
    <h2>Address</h2>
    <label for="street">Street: </label>
    <input id="street" type="text" formControlName="street">

    <label for="city">City: </label>
    <input id="city" type="text" formControlName="city">

    <label for="state">State: </label>
    <input id="state" type="text" formControlName="state">

    <label for="zip">Zip Code: </label>
    <input id="zip" type="text" formControlName="zip">
  </div>

  <button type="button" (click)="updateProfile()">Update Profile</button>

  <p>Complete o formulário para habilitar o botão.</p>
  <button type="submit" [disabled]="!profileForm.valid">Submit</button>
</form>

Quando um usuário clica no botão, o modelo profileForm é atualizado com novos valores para firstName e street. Observe que street é fornecido em um objeto dentro da propriedade address.
Isso é necessário porque o método patchValue() aplica a atualização contra a estrutura do modelo.
PatchValue() apenas atualiza as propriedades que o modelo do formulário define.

Usando o Serviço FormBuilder para Gerar Controles

Criar instâncias de controle de formulário manualmente pode se tornar repetitivo ao lidar com vários formulários. O serviço FormBuilder fornece métodos convenientes para gerar controles.

Use os seguintes passos para aproveitar este serviço.

  1. Importe a classe FormBuilder.
  2. Injeção do serviço FormBuilder.
  3. Gere o conteúdo do formulário.

Os exemplos a seguir mostram como refatorar o componente ProfileEditor para usar o serviço FormBuilder para criar instâncias de controle de formulário e grupo de formulário.

Importar a Classe FormBuilder

Importe a classe FormBuilder do pacote @angular/forms.

Código:
// src/app/profile-editor/profile-editor.component.ts

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

Injetar o Serviço FormBuilder

O serviço FormBuilder é um provedor injetável que é fornecido com o módulo de formulários reativos. Injete essa dependência adicionando-a ao construtor do componente.

export class ProfileEditorComponent {
  constructor(private formBuilder: FormBuilder) {}

  // ...
}

Gerar Controles de Formulário

O serviço FormBuilder tem três métodos: control(), group() e array(). Estes são métodos de fábrica para gerar instâncias em suas classes de componente, incluindo controles de formulário, grupos de formulário e arrays de formulário. Use o método group para criar os controles profileForm.

Código:

gora você pode usar os métodos control(), group() e array() do FormBuilder para criar seus controles:

profileForm = this.formBuilder.group({
  firstName: [''],
  lastName: [''],
  address: this.formBuilder.group({
    street: [''],
    city: [''],
    state: [''],
    zip: [''],
  })
});

Explicação do Código

  • this.formBuilder.group({}): Cria um novo FormGroup. O objeto passado como argumento define a estrutura do grupo, com cada chave representando o nome de um controle e o valor sendo um array com:
    • O valor inicial do controle (neste caso, strings vazias).
    • Opcionalmente, validadores síncronos (segundo elemento do array).
    • Opcionalmente, validadores assíncronos (terceiro elemento do array).
  • this.formBuilder.control(''): Cria um novo FormControl com o valor inicial especificado.

Comparação com a Criação Manual

Criação ManualCriação com FormBuilder
new FormGroup({ firstName: new FormControl(”) })this.formBuilder.group({ firstName: [”] })
new FormGroup({ address: new FormGroup({ street: new FormControl(”) }) })this.formBuilder.group({ address: this.formBuilder.group({ street: [”] }) })
Compare o uso do FormBuilder com a criação das instâncias manualmente.
import { Component } from '@angular/core';
import {FormBuilder, FormControl, FormGroup, ReactiveFormsModule} from "@angular/forms";

@Component({
  selector: 'app-profile-editor',
  standalone: true,
  imports: [
    ReactiveFormsModule
  ],
  template: `
    <form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
      <label for="first-name">First Name: </label>
      <input id="first-name" type="text" formControlName="firstName">

      <label for="last-name">Last Name: </label>
      <input id="last-name" type="text" formControlName="lastName">

      <div formGroupName="address">
        <h2>Address</h2>
        <label for="street">Street: </label>
        <input id="street" type="text" formControlName="street">

        <label for="city">City: </label>
        <input id="city" type="text" formControlName="city">

        <label for="state">State: </label>
        <input id="state" type="text" formControlName="state">

        <label for="zip">Zip Code: </label>
        <input id="zip" type="text" formControlName="zip">
      </div>
      <button type="button" (click)="updateProfile()">Update Profile</button> 
      <p>Complete o formulário para habilitar o botão.</p>
      <button type="submit" [disabled]="!profileForm.valid">Submit</button>
    </form>
  `,
  styles: ``
})
export class ProfileEditorComponent {
  constructor(private formBuilder: FormBuilder) {}

  profileForm = this.formBuilder.group({
    firstName: [''],
    lastName: [''],
    address: this.formBuilder.group({
      street: [''],
      city: [''],
      state: [''],
      zip: ['']
    })
  });

  onSubmit() {
    // TODO: Use EventEmitter with form value
    console.warn(this.profileForm.value);
  }

  updateProfile() {
    this.profileForm.patchValue({
      firstName: 'Nancy',
      address: {
        street: '123 Drew Street'
      }
    });
  }
}

Validando Entradas do Formulário

A validação de formulário é fundamental para garantir que os dados inseridos pelo usuário estejam completos e corretos. Vamos começar adicionando um validador simples: o campo obrigatório.

Importar uma Função de Validador

Os formulários reativos incluem um conjunto de funções de validador para casos de uso comuns. Essas funções recebem um controle para validar e retornam um objeto de erro ou um valor nulo com base na verificação de validação.

Importe a classe Validators do pacote @angular/forms.

// reactive-forms-module/src/app/components/profile-editor/profile-editor.component.ts

import { Component } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
Tornar um Campo Obrigatório

No componente ProfileEditor, adicione o método estático Validators.required como o segundo item no array para o controle firstName.

profileForm = this.formBuilder.group({
  firstName: ['', Validators.required], // Campo obrigatório
  // ... outros campos ...
});

Exibir o Status do Formulário

Quando você adiciona um campo obrigatório ao controle de formulário, seu status inicial é inválido. Esse status inválido é propagado para o elemento do grupo de formulário pai, tornando seu status inválido. Acesse o status atual da instância do grupo de formulário através de sua propriedade status.

Exiba o status atual do profileForm usando interpolação.

Código:
<p>Form Status: {{ profileForm.status }}</p>

O botão Submit está desativado porque profileForm é inválido devido ao controle firstName obrigatório. Depois de preencher o campo firstName, o formulário se torna válido e o botão Submit é habilitado.

<button type="submit" [disabled]="!profileForm.valid">Submit</button>

O botão Submit está desativado porque profileForm é inválido devido ao controle firstName obrigatório. Depois de preencher o campo firstName, o formulário se torna válido e o botão Submit é habilitado.

Explicações Detalhadas

  • Validators: Classe que contém funções de validação prontas para uso, como required, email, minLength, etc.
  • Validators.required: Função validadora que verifica se o campo não está vazio.
  • profileForm.status: Propriedade do FormGroup que retorna o status atual do formulário (VALID, INVALID, PENDING, DISABLED).
  • profileForm.valid: Propriedade do FormGroup que retorna true se todos os controles do formulário forem válidos e false caso contrário.

Criando Formulários Dinâmicos com FormArray

FormArray é uma alternativa ao FormGroup para gerenciar qualquer número de controles não nomeados. Assim como instâncias de grupos de formulário, você pode inserir e remover controles dinamicamente de instâncias de arrays de formulário, e o valor e o status de validação da instância do array de formulário são calculados a partir de seus controles filhos.
No entanto, você não precisa definir uma chave para cada controle pelo nome, então essa é uma ótima opção se você não souber o número de valores filhos com antecedência.

Para definir um formulário dinâmico, siga os seguintes passos:

  1. Importe a classe FormArray.
  2. Defina um controle FormArray.
  3. Acesse o controle FormArray com um método getter.
  4. Exiba o array de formulário em um template.

O exemplo a seguir mostra como gerenciar um array de aliases no ProfileEditor.

Importar a Classe FormArray

Importe a classe FormArray do pacote @angular/forms para usar nas informações de tipo. O serviço FormBuilder está pronto para criar uma instância de FormArray.

Código:
import { FormBuilder, Validators, FormArray } from '@angular/forms';

Definir um Controle FormArray

Você pode inicializar um array de formulário com qualquer número de controles, de zero a muitos, definindo-os em um array. Adicione uma propriedade aliases à instância do grupo de formulário profileForm para definir o array de formulário.

Use o método FormBuilder.array() para definir o array e o método FormBuilder.control() para preencher o array com um controle inicial.

Código:
profileForm = this.formBuilder.group({
  // ... outros campos ...
  aliases: this.formBuilder.array([
    this.formBuilder.control('') // Controle inicial para o primeiro alias
  ])
});

Criar um Getter para o FormArray

Acessar o Controle FormArray

Um getter fornece acesso aos aliases na instância do array de formulário em comparação com a repetição do método profileForm.get() para obter cada instância.
A instância do array de formulário representa um número indefinido de controles em um array. É conveniente acessar um controle através de um getter, e essa abordagem é fácil de repetir para controles adicionais.

Use a sintaxe de getter para criar uma propriedade de classe aliases para recuperar o controle do array de formulário dos aliases a partir do grupo de formulário pai.

get aliases() {
  return this.profileForm.get('aliases') as FormArray;
}

Adicionar um Método para Inserir um Novo Alias

Como o controle retornado é do tipo AbstractControl, você precisa fornecer um tipo explícito para acessar a sintaxe do método para a instância do array de formulário. Defina um método para inserir dinamicamente um controle de alias no array de aliases. O método FormArray.push() insere o controle como um novo item no array.

addAlias() {
  this.aliases.push(this.formBuilder.control(''));
}

O controle aliases na instância do grupo de formulário agora está populado com um controle único até que mais controles sejam adicionados dinamicamente.

Exibir o Array de Formulário no Template

Para anexar os aliases do seu modelo de formulário, você deve adicioná-lo ao template. Semelhante ao formGroupName fornecido pela FormGroupNameDirective, formArrayName vincula a comunicação da instância do array de formulário ao template com a FormArrayNameDirective.

Adicione o seguinte HTML ao template após o fechamento da tag div do elemento formGroupName.

Código:

<div formArrayName="aliases">
  <h2>Aliases</h2>
  <button type="button" (click)="addAlias()">Add Alias</button>

  <div *ngFor="let alias of aliases.controls; let i=index">
    <label for="alias-{{ i }}">Alias:</label>
    <input id="alias-{{ i }}" type="text" [formControlName]="i">
  </div>
</div>

Explicações Detalhadas

A diretiva *ngFor itera sobre cada instância de controle de formulário fornecida pela instância do array de formulário aliases. Como os elementos do array de formulário não são nomeados, você atribui o índice à variável i e passa para cada controle para vinculá-lo à entrada formControlName.

Cada vez que uma nova instância de alias é adicionada, a nova instância do array de formulário é fornecida com seu controle baseado no índice. Isso permite rastrear cada controle individual ao calcular o status e o valor do controle raiz.

  • FormArray: Uma classe que representa um array de controles de formulário.
  • formBuilder.array(): Método do FormBuilder para criar um novo FormArray.
  • formArrayName: Diretiva que vincula um elemento HTML a um FormArray no componente.
  • aliases.controls: Propriedade do FormArray que retorna um array com os controles filhos.
  • [formControlName]="i": Vincula cada campo de entrada ao controle correspondente no FormArray, usando o índice i do loop *ngFor.

Adicionar um Alias

Inicialmente, o formulário contém um campo Alias. Para adicionar outro campo, clique no botão Add Alias. Você também pode validar o array de aliases relatado pelo modelo de formulário exibido pelo Form Value na parte inferior do template.
Em vez de uma instância de controle de formulário para cada alias, você pode compor outra instância de grupo de formulário com campos adicionais. O processo de definir um controle para cada item é o mesmo.

Exemplo Completo

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

@Component({
  selector: 'app-profile-editor',
  standalone: true,
  imports: [
    ReactiveFormsModule,
    NgForOf
  ],
  template: `
    <form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
      <label for="first-name">First Name: </label>
      <input id="first-name" type="text" formControlName="firstName">

      <label for="last-name">Last Name: </label>
      <input id="last-name" type="text" formControlName="lastName">

      <div formGroupName="address">
        <h2>Address</h2>
        <label for="street">Street: </label>
        <input id="street" type="text" formControlName="street">

        <label for="city">City: </label>
        <input id="city" type="text" formControlName="city">

        <label for="state">State: </label>
        <input id="state" type="text" formControlName="state">

        <label for="zip">Zip Code: </label>
        <input id="zip" type="text" formControlName="zip">
      </div>

      <div formArrayName="aliases">
        <h2>Aliases</h2>
        <button type="button" (click)="addAlias()">Add Alias</button>

        <div *ngFor="let alias of aliases.controls; let i=index">
          <label for="alias-{{ i }}">Alias:</label>
          <input id="alias-{{ i }}" type="text" [formControlName]="i">
        </div>
      </div>      

      <button type="button" (click)="updateProfile()">Update Profile</button>
      <p>Form Status: {{ profileForm.status }}</p>
      <p>Complete o formulário para habilitar o botão.</p>
      <button type="submit" [disabled]="!profileForm.valid">Submit</button>
    </form>
  `,
  styles: ``
})
export class ProfileEditorComponent {
  constructor(private formBuilder: FormBuilder) {}

  profileForm = this.formBuilder.group({
    firstName: ['',Validators.required],
    lastName: [''],
    address: this.formBuilder.group({
      street: [''],
      city: [''],
      state: [''],
      zip: ['']
    }),
    aliases: this.formBuilder.array([this.formBuilder.control('')])
  });

  onSubmit() {
    // TODO: Use EventEmitter with form value
    console.warn(this.profileForm.value);
  }

  updateProfile() {
    this.profileForm.patchValue({
      firstName: 'Nancy',
      address: {
        street: '123 Drew Street'
      }
    });
  }

  get aliases() {
    return this.profileForm.get('aliases') as FormArray;
  }

  addAlias() {
    this.aliases.push(this.formBuilder.control(''));
  }

}

Resumo da API de Formulários Reativos do Angular

A tabela a seguir resume as classes e diretivas principais usadas para criar e gerenciar formulários reativos no Angular. Para detalhes completos da sintaxe, consulte a documentação de referência da API para o pacote @angular/forms.

Classes Base e Serviços

ClasseDescrição
AbstractControlClasse base abstrata para as classes concretas FormControl, FormGroup e FormArray. Fornece comportamentos e propriedades comuns.
FormControlGerencia o valor e o status de validade de um controle de formulário individual (input, select, textarea).
FormGroupGerencia o valor e o status de validade de um grupo de instâncias de AbstractControl.
FormArrayGerencia o valor e o status de validade de um array de instâncias de AbstractControl.
FormBuilderServiço injetável que fornece métodos para criar instâncias de controles de forma mais concisa.
FormRecordGerencia o valor e o status de validade de uma coleção de instâncias de FormControl com o mesmo tipo de valor.

Diretivas

DiretivaDetalhes
FormControlDirectiveSincroniza uma instância de FormControl autônoma com um elemento de controle de formulário.
FormControlNameSincroniza FormControl em uma instância FormGroup existente com um elemento de controle de formulário pelo nome.
FormGroupDirectiveSincroniza uma instância FormGroup existente com um elemento DOM.
FormGroupNameSincroniza uma instância FormGroup aninhada com um elemento DOM.
FormArrayNameSincroniza uma instância FormArray aninhada com um elemento DOM.

Neste guia, exploramos como criar e gerenciar formulários reativos no Angular, incluindo a adição de controles básicos, a criação de grupos de controles, a validação de entradas e a construção de formulários dinâmicos. Utilizamos o serviço FormBuilder para simplificar a criação de controles e discutimos a importância de usar FormGroup e FormArray para organizar e gerenciar grupos de controles de formulário.

Os formulários reativos fornecem uma abordagem poderosa e flexível para gerenciar a entrada do usuário em suas aplicações Angular.
A abordagem baseada em modelos e a integração com observáveis tornam a sincronização de dados e o rastreamento de mudanças uma tarefa direta e eficiente. Com a capacidade de adicionar validações, agrupar controles e criar formulários dinâmicos, os formulários reativos são uma escolha excelente para aplicações de qualquer tamanho e complexidade.

Que esta jornada pelo mundo dos formulários reativos no Angular seja apenas o começo da sua odisseia pela maestria. Que cada formulário que você criar seja um monumento à sua dedicação, um testemunho do seu talento e uma fonte de inspiração para outros exploradores do universo digital.

Desvende os Segredos do Código:

Para aprofundar sua jornada e explorar os exemplos completos deste guia, convido você a visitar o repositório no GitHub: