Reactive forms vs Template-driven forms: qual escolher?

Pessoa utilizando um notebook no colo enquanto está sentada em um sofá, com uma aba de plataforma de cursos aberta na tela. À esquerda da imagem, há elementos gráficos representando a Escola de Front-end da Alura. A identidade visual inclui um contorno de montanha em azul e logotipo da Alura no canto inferior direito
Rafaela Petelin Silvério
Rafaela Petelin Silvério

Compartilhe

Fala, Dev!  

Se você está entrando no mundo Angular e chegou o momento de construir formulários, saiba que nesse framework temos duas principais abordagens: template-driven forms e reactive forms

Ambas fazem o “arroz com feijão” dos formulários, ou seja: capturar e validar dados do usuário. Mas cada uma segue seu próprio método: uma prioriza a simplicidade do HTML e, a outra, o controle do TypeScript. 

Escolher entre elas não vai depender só do seu gosto, porque essa decisão impacta diretamente a manutenção, complexidade e até os testes da sua aplicação. 

Neste artigo, vamos comparar essas duas abordagens utilizando um projeto simples como exemplo: um formulário de cadastro, para você sair daqui dominando as características dessas abordagens: 

Interface Angular com abas; uma exibe formulário Template-Driven e outra, formulário Reativo

Pronto para essa aventura? 

O que são reactive forms e template-driven forms? 

O que é template-driven forms? 

O template-driven forms (em português, “formulário orientado a template") é uma abordagem do Angular em que o formulário é feito através do template, ou seja, no arquivo HTML.  

Aqui, para fazer a vinculação bidirecional (*two-way data binding*) entre os campos e o modelo de dados, usamos diretivas, por exemplo, o `[(ngModel)]`. 

O `[(ngModel)]` é uma sintaxe conhecida como "banana in a box"(banana na caixa, em português), que combina property binding `([ ])` e event binding `(( ))`. 

Isso permite uma comunicação de mão dupla: 

  • Quando o usuário digita algo no campo, o valor é atualizado automaticamente no modelo (componente). 
  • Quando o valor do modelo muda no componente, a view (o HTML) também é atualizada automaticamente. 

Por exemplo: 

```  
<input [(ngModel)]="usuario.nome"> 
```

Neste caso, qualquer mudança no input será refletida em `usuario.nome`, e qualquer alteração feita em `usuario.nome` também será refletida no campo. 

É uma abordagem é ideal para formulários simples, pois exige menos código no arquivo TypeScript. 

Como usar template-driven forms? 

Vamos entender melhor, aplicando essa abordagem na criação de um formulário de cadastro de usuário com os seguintes campos e validações: 

  • Nome (obrigatório, mínimo de 3 caracteres); 
  • Email (obrigatório, formato de email válido); 
  • Tipo de conta (obrigatório – comum ou admin); 
  • Data de nascimento (obrigatório); 
  • Receber newsletter (opcional). 

O objetivo é chegar ao resultado abaixo: 

Imagem de um formulário web em Angular com o título "Cadastro - Template-Driven", exibindo campos de nome, email, tipo de conta, data de nascimento, checkbox para newsletter e botão "Enviar"

Vamos configurar esse formulário orientado a template? 

Configuração inicial do projeto  

Para começarmos, criei um projeto Angular na versão 19 e dentro dele criei dois componentes: 

  • `FormTemplateComponent`, no qual desenvolveremos nosso formulário template-driven, que vamos manipular agora; 
  • `FormReativoComponent`, onde vamos construir o formulário reativo mais à frente. 

Você pode seguir os comandos apresentados abaixo ou baixar essa base de projeto, já com os componentes criados e estilizações CSS inclusos [através desse link]. 

Se quiser testar os códigos apresentados, mas sem fazer a instalação do Angular CLI 19 e a configuração do projeto diretamente em sua máquina, utilize a ferramenta [IDX](https://idx.google.com/). 

Para criar o projeto, usei o comando a seguir no terminal, dentro da pasta desejada: 

```bash 
ng new forms 
```

Ao longo da criação do projeto, receberemos 3 perguntas:  

  • A primeira pergunta se desejamos usar o formato CSS (respondi que sim);  
  • A segunda questiona se queremos habilitar o *Server-Side Rendering* (SSR) e o Static Site Generation (SSG/Prerendering) (marquei Yes, visando a melhorar performance e SEO);  
  • E a terceira pergunta se queremos utilizar as APIs de Server Routing e App Engine (Developer Preview); escolhi Yes para as APIs de servidor, garantindo suporte completo ao backend.  

Recomendo seguir essas escolhas para evitar incompatibilidades, exceto na escolha de estilo, que fica conforme a sua preferência. 

Em seguida, abrimos nosso projeto: 

```bash 
cd forms 
ng version  # Para confirmar a versão (deve ser 19) 
```

Obs.: Certifique-se de que o Angular CLI 19 está instalado. Se precisar instalar essa versão específica: 

```bash 
npm install -g @angular/cli@19 
```

Utilize os seguintes comandos para criar os componentes: 

```bash 
ng generate component form-template    
ng generate component form-reativo 
```

Com a base pronta, vamos configurar nosso projeto no arquivo `main.ts`, como pode observar a seguir: 

``` 
import { bootstrapApplication } from '@angular/platform-browser';  
import { AppComponent } from './app/app.component';  
import { importProvidersFrom } from '@angular/core';  
import { FormsModule, ReactiveFormsModule } from '@angular/forms';//módulos para formulário  
import { provideRouter } from '@angular/router';  
import { FormTemplateComponent } from './app/form-template/form-template.component';  
import { FormReativoComponent } from './app/form-reativo/form-reativo.component';  
bootstrapApplication(AppComponent, {  
  providers: [  
    importProvidersFrom(FormsModule, ReactiveFormsModule),  
    provideRouter([  
      { path: 'template', component: FormTemplateComponent },  
      { path: 'reativo', component: FormReativoComponent },  
    ]),  
  ],  
}); 
```

Nesse arquivo, importei os módulos que precisaremos para os formulários.  

Observe que, para nosso formulário *template-driven*, usaremos o `FormsModule`.   

Também já me adiantei e adicionei o módulo `ReactiveFormsModule`,  para o formulário reativo (a outra abordagem que conheceremos melhor mais à frente).  

Também criei duas rotas, uma para cada formulário em nossa aplicação. Feito isso, podemos preparar nosso formulário template-driven.  

Configuração do formulário Template-Driven 
 

Agora, vamos criar o HTML do componente `FormTemplateComponent`, no arquivo `form-template.component.html`.  

Perceba que essa abordagem é mais declarativa. A lógica de validação e o estado do formulário são gerenciados principalmente no template HTML, no qual precisaremos utilizar a diretiva `[(ngModel)]` para fazer *data binding bidirecional* entre o `input` e o modelo de dados.  

Para as validações, podemos usar os próprios atributos HTML nativos como `required`, `minlength`, ou `type="email"`. As mensagens de erro são exibidas condicionalmente com `*ngIf` se determinada condição de validação não é atendida. 

Além disso, cada campo do formulário pode ter sua própria referência local (como `#nome="ngModel"`) para acessarmos seu estado de validação diretamente no template.  

E quanto ao envio do formulário, o botão só ficará habilitado quando `formulario.valid `for verdadeiro, ou seja, quando todos os campos estiverem válidos.  

Em código, teremos o seguinte: 

```html 
<h2>Cadastro - Template-Driven</h2>  
<form #formulario="ngForm" (ngSubmit)="aoEnviar()" novalidate>  
  <label>  
    Nome:  
    <input name="nome" [(ngModel)]="usuario.nome" required minlength="3" #nome="ngModel" />  
    <div *ngIf="nome.invalid && nome.touched">  
      <small *ngIf="nome.errors?.['required']">Nome é obrigatório.</small>  
      <small *ngIf="nome.errors?.['minlength']">Mínimo de 3 caracteres.</small>  
    </div>  
  </label>  
  <label>  
    Email:  
    <input name="email" type="email" [(ngModel)]="usuario.email" required #email="ngModel" />  
    <div *ngIf="email.invalid && email.touched">  
      <small *ngIf="email.errors?.['required']">Email é obrigatório.</small>  
      <small *ngIf="email.errors?.['email']">Formato inválido.</small>  
    </div>  
  </label>  
  <label>  
    Tipo de conta:  
    <select name="tipoConta" [(ngModel)]="usuario.tipoConta" required #tipoConta="ngModel">  
      <option value="">Selecione</option>  
      <option value="comum">Comum</option>  
      <option value="admin">Admin</option>  
    </select>  
    <div *ngIf="tipoConta.invalid && tipoConta.touched">  
      <small>Tipo de conta é obrigatório.</small>  
    </div>  
  </label>  
  <label>  
    Data de nascimento:  
    <input name="dataNascimento" type="date" [(ngModel)]="usuario.dataNascimento" required #dataNascimento="ngModel" />  
    <div *ngIf="dataNascimento.invalid && dataNascimento.touched">  
      <small>Data de nascimento é obrigatória.</small>  
    </div>  
  </label>  
  <label class="checkbox-label">  
    <input name="receberNewsletter" type="checkbox" [(ngModel)]="usuario.receberNewsletter" />  
    <span>Deseja receber nossa newsletter?</span>  
  </label>  
  <button type="submit" [disabled]="formulario.invalid">Enviar</button>  
</form> 
```

No arquivo TypeScript, precisamos importar o `FormsModule` para habilitar todas as funcionalidades do formulário e definir o modelo de dados que será vinculado ao template, neste caso, `usuario`. E podemos definir quantas funções forem necessárias para lidar com o formulário, como a função de envio `aoEnviar()`: 

```ts 
import { Component } from '@angular/core';  
import { FormsModule } from '@angular/forms';  
import { CommonModule } from '@angular/common';  
@Component({  
  selector: 'app-form-template',  
  standalone: true,  
  imports: [CommonModule, FormsModule],  
  templateUrl: './form-template.component.html',  
  styleUrls: ['./form-template.component.css'],  
})  
export class FormTemplateComponent {  
  usuario = {  
    nome: '',  
    email: '',  
    tipoConta: '',  
    dataNascimento: '',  
    receberNewsletter: false,  
  };  
  aoEnviar() {  
    alert(  
      `Formulário enviado (template-driven): ${this.usuario.nome} teve seu cadastro bem sucedido!`  
    );  
  }  
} 
```

E nosso formulário template-driven está pronto e funcional: 

Formulário Template-Driven em ação: validação e envio no Angular

Observando esse padrão, já dá pra imaginar algumas vantagens e desvantagens de usá-lo, não é mesmo? Listei esses pontos a seguir. 

Quais as vantagens do template-driven forms? 

Entre as vantagens do template-driven forms, cabe-se citar:

  • Menor curva de aprendizado, ou seja, mais amigável para iniciantes; 
  • Configuração rápida, o que favorece a criação de formulários mais simples; 
  • Código mais enxuto no TypeScript. 

Quais as desvantagens do template-driven forms? 

Entre as desvantagens, é possível notar que:

  • É mais difícil testar a lógica no formulário, por estar acoplado ao template, como discutiremos mais à frente; 
  • Menor controle das validações, que precisam ser adicionadas no template; 
  • O código fica mais espalhado entre os arquivos HTML e TS. 

Vamos continuar nosso projeto reproduzindo esse formulário, agora na abordagem Reativa? 

Reactive forms 

Agora que já conhecemos o formulário Template-Driven, podemos conhecer o outro lado dessa batalha: na abordagem dos Reactive forms, o controle do formulário é construído através do componente TypeScript.  

Para fazer isso, usamos as APIs `FormGroup`, `FormControl` e `FormBuilder` para montar o formulário direto no TypeScript, onde vamos definir os campos e todas as suas regras, como as validações, por exemplo.  

Para entender melhor, continuaremos nosso projeto. Nesse caso, o arquivo TypeScript assume a maior parte das responsabilidades. Vamos ver? 
 

Configuração do formulário reativo 

No arquivo `form-reativo.component.ts`, criei o seguinte código, onde primeiro importamos os recursos necessários para trabalhar com formulários reativos no Angular, como `FormBuilder`, `FormGroup` e `Validators`, além dos módulos `ReactiveFormsModule` e `CommonModule`.  

Em seguida, injetamos o `FormBuilder` no construtor da classe para facilitar a criação do formulário. Dentro do método `ngOnInit`, utilizamos o `fb.group()` para construir o `FormGroup`, definindo cada campo (`nome`, `email`, `tipoConta`, `dataNascimento` e `receberNewsletter`) com seus valores iniciais e validadores apropriados, como `Validators.required` e `Validators.email`. 

 Por fim, criamos a função `aoEnviar()`, que é acionada ao enviar o formulário e exibe um alerta com o nome preenchido, acessando os dados através de `this.formularioUsuario.value`. 

Além disso, para tornar o código mais organizado, foi criada uma interface `Usuario`, que define claramente os campos do formulário e seus respectivos tipos. Isso permite garantir, em tempo de desenvolvimento, que os dados manipulados pelo formulário estejam corretos e sigam uma estrutura bem definida. 

Além disso, para melhorar a leitura do template e evitar repetições desnecessárias no HTML, foram implementados métodos `getters` no componente. Assim, o acesso aos controles do formulário se torna mais direto, melhorando a legibilidade e a manutenção do código. 

Observe: 

```ts 
import { Component, OnInit } from '@angular/core'; 
import { 
  FormBuilder, 
  FormGroup, 
  Validators, 
  ReactiveFormsModule, 
} from '@angular/forms'; 
import { CommonModule } from '@angular/common'; 
interface Usuario { 
  nome: string; 
  email: string; 
  tipoConta: string; 
  dataNascimento: string; 
  receberNewsletter: boolean; 
} 
@Component({ 
  selector: 'app-form-reativo', 
  standalone: true, 
  imports: [CommonModule, ReactiveFormsModule], 
  templateUrl: './form-reativo.component.html', 
  styleUrls: ['./form-reativo.component.css'], 
}) 
export class FormReativoComponent implements OnInit { 
  formularioUsuario!: FormGroup; 
  constructor(private fb: FormBuilder) {} 
  ngOnInit() { 
    this.criarFormulario(); 
  } 
  private criarFormulario(): void { 
    this.formularioUsuario = this.fb.group({ 
      nome: ['', [Validators.required, Validators.minLength(3)]], 
      email: ['', [Validators.required, Validators.email]], 
      tipoConta: ['', Validators.required], 
      dataNascimento: ['', Validators.required], 
      receberNewsletter: [false], 
    }); 
  } 
  aoEnviar() { 
    const usuario: Usuario = this.formularioUsuario.value as Usuario; // Tipagem explícita 
    alert( 
      `Formulário enviado (reativo): ${usuario.nome} teve seu cadastro bem sucedido!` 
    ); 
  } 
  get nome() { 
    return this.formularioUsuario.get('nome'); 
  } 
  get email() { 
    return this.formularioUsuario.get('email'); 
  } 
  get tipoConta() { 
    return this.formularioUsuario.get('tipoConta'); 
  } 
  get dataNascimento() { 
    return this.formularioUsuario.get('dataNascimento'); 
  } 
  get receberNewsletter() { 
    return this.formularioUsuario.get('receberNewsletter'); 
  } 
} 
```

O template utilizado (arquivo `form-reativo.component.html`) define a estrutura do formulário no HTML. Nele, usamos a diretiva `[formGroup]="formularioUsuario"` para conectar o formulário visual ao `FormGroup` criado no TypeScript. Cada campo (como `nome`, `email`, `tipoConta` e `dataNascimento`) é associado ao seu controle específico usando `formControlName`.  

As validações são gerenciadas pelo Angular: o código TypeScript define as regras e, no HTML, mostramos mensagens de erro de forma condicional usando `*ngIf`, que verifica se o campo está inválido (`invalid`) e foi tocado (`touched`).  

Dessa forma, as mensagens só aparecem depois que o usuário interage com os campos. O botão de envio é controlado pela propriedade `[disabled]`, ficando desabilitado enquanto o formulário for considerado inválido. Segue o código abaixo: 

``` 
<h2>Cadastro - Formulário Reativo</h2> 
<form [formGroup]="formularioUsuario" (ngSubmit)="aoEnviar()" novalidate> 
  <label> 
    Nome: 
    <input formControlName="nome" /> 
    <div *ngIf="nome?.invalid && nome?.touched"> 
      <small *ngIf="nome?.errors?.['required']">Nome é obrigatório.</small> 
      <small *ngIf="nome?.errors?.['minlength']">Mínimo de 3 caracteres.</small> 
    </div> 
  </label> 
  <label> 
    Email: 
    <input formControlName="email" type="email" /> 
    <div *ngIf="email?.invalid && email?.touched"> 
      <small *ngIf="email?.errors?.['required']">Email é obrigatório.</small> 
      <small *ngIf="email?.errors?.['email']">Formato inválido.</small> 
    </div> 
  </label> 
  <label> 
    Tipo de conta: 
    <select formControlName="tipoConta"> 
      <option value="">Selecione</option> 
      <option value="comum">Comum</option> 
      <option value="admin">Admin</option> 
    </select> 
    <div *ngIf="tipoConta?.invalid && tipoConta?.touched"> 
      <small>Tipo de conta é obrigatório.</small> 
    </div> 
  </label> 
  <label> 
    Data de nascimento: 
    <input type="date" formControlName="dataNascimento" /> 
    <div *ngIf="dataNascimento?.invalid && dataNascimento?.touched"> 
      <small>Data de nascimento é obrigatória.</small> 
    </div> 
  </label> 
  <label class="checkbox-label"> 
    <input type="checkbox" formControlName="receberNewsletter" /> 
    <span>Deseja receber nossa newsletter?</span> 
  </label> 
  <button type="submit" [disabled]="formularioUsuario.invalid">Enviar</button> 
</form> 
```

Temos nosso formulário Reativo pronto também: 

Formulário Reativo em ação: validação e envio no Angular

Dicas extras – outras possibilidades com formulários reativos 

Adicionando validações customizadas 

Além das validações padrão, como `required` e `email`, também é possível criar validações personalizadas no Angular para atender regras específicas de negócio. 
 
Aqui, implementamos uma validação para o campo "Data de nascimento", que garante que o usuário não possa selecionar uma data no futuro. 
 
Essa validação foi feita por meio de um método estático dentro do próprio componente, chamado `validarData()`. Essa função recebe o valor do controle, transforma em um objeto `Date` e compara com a data atual. Se a data for futura, ela retorna um erro no formato `{ dataFutura: true }`. Caso contrário, retorna `null`, indicando que o valor é válido. 
 
A aplicação da validação no campo ocorre da seguinte forma: 

```ts 
dataNascimento: ['', [Validators.required, FormReativoComponent.validarData]], 
``` 

No template, também foi adicionada uma mensagem específica para esse erro personalizado, garantindo uma comunicação clara com quem preenche o formulário: 

``` 
<small *ngIf="dataNascimento?.errors?.['dataFutura']"> 
  A data de nascimento não pode ser no futuro. 
</small> 
```

A função da validação personalizada fica assim: 

``` 
static validarData(control: AbstractControl): ValidationErrors | null { 
  const data = new Date(control.value); 
  const hoje = new Date(); 
  return data > hoje ? { dataFutura: true } : null; 
} 
  
```

Como ficou o trecho no template, com a validação de data vazia e de data futura: 

``` 
<label> 
  Data de nascimento: 
  <input type="date" formControlName="dataNascimento" /> 
  <div *ngIf="dataNascimento?.invalid && dataNascimento?.touched"> 
    <small *ngIf="dataNascimento?.errors?.['required']"> 
      Data de nascimento é obrigatória. 
    </small> 
    <small *ngIf="dataNascimento?.errors?.['dataFutura']"> 
      A data de nascimento não pode ser no futuro. 
    </small> 
  </div> 
</label> 
```

Com a validação implementada, o código completo do arquivo ts ficou assim: 

``` 
import { Component, OnInit } from '@angular/core'; 
import { 
FormBuilder, 
  FormGroup, 
  Validators, 
  AbstractControl, 
  ValidationErrors, 
  ReactiveFormsModule, 
} from '@angular/forms'; 
import { CommonModule } from '@angular/common'; 
 
interface Usuario { 
  nome: string; 
  email: string; 
  tipoConta: string; 
  dataNascimento: string; 
  receberNewsletter: boolean; 
} 
 
@Component({ 
  selector: 'app-form-reativo', 
  standalone: true, 
  imports: [CommonModule, ReactiveFormsModule], 
  templateUrl: './form-reativo.component.html', 
  styleUrls: ['./form-reativo.component.css'], 
}) 
export class FormReativoComponent implements OnInit { 
   
  formularioUsuario!: FormGroup; 
 
  constructor(private fb: FormBuilder) {} 
 
  ngOnInit() { 
    this.criarFormulario(); 
  } 
 
  private criarFormulario(): void { 
    this.formularioUsuario = this.fb.group({ 
      nome: ['', [Validators.required, Validators.minLength(3)]], 
      email: ['', [Validators.required, Validators.email]], 
      tipoConta: ['', Validators.required], 
      dataNascimento: ['', [Validators.required, FormReativoComponent.validarData]], 
      receberNewsletter: [false], 
    }); 
  } 
 
  // Validação personalizada para datas futuras 
  static validarData(control: AbstractControl): ValidationErrors | null { 
    const data = new Date(control.value); 
    const hoje = new Date(); 
    return data > hoje ? { dataFutura: true } : null; 
  } 
 
  aoEnviar() { 
    const usuario: Usuario = this.formularioUsuario.value as Usuario; 
    alert( 
      `Formulário enviado (reativo): ${usuario.nome} teve seu cadastro bem sucedido!` 
    ); 
  } 
 
  // Getters para simplificar o template 
  get nome() { 
    return this.formularioUsuario.get('nome'); 
  } 
 
  get email() { 
    return this.formularioUsuario.get('email'); 
  } 
 
  get tipoConta() { 
    return this.formularioUsuario.get('tipoConta'); 
  } 
 
  get dataNascimento() { 
    return this.formularioUsuario.get('dataNascimento'); 
  } 
 
  get receberNewsletter() { 
    return this.formularioUsuario.get('receberNewsletter'); 
  } 
} 
  
```

O resultado é uma validação de data que "reclama” se a data inserida for após a data atual: 

Imagem de um formulário de cadastro no Angular com tema escuro. O campo "Data de nascimento" está preenchido com uma data futura (07/08/2025) e exibe uma mensagem de erro em texto vermelho: "A data de nascimento não pode ser no futuro", indicando a ativação da validação personalizada para impedir datas futuras.
Banner da Imersão Front-end com IA da Alura convidando para aprender front-end com inteligência artificial gratuitamente. A proposta é desenvolver na prática uma página de streaming funcional para portfólio, com botão “Saber mais”. Curso online voltado para quem quer iniciar ou evoluir na carreira em desenvolvimento front-end com IA.

Trabalhando com FormArray no Angular 

Nem sempre sabemos de antemão quantos itens um formulário precisa capturar. Existem situações em que o usuário pode querer informar uma quantidade variável de dados, como telefones, dependentes, documentos ou habilidades.  

Nesses casos, usamos o FormArray, que permite gerenciar dinamicamente uma lista de controles dentro de um formulário reativo no Angular. 

Por que usar FormArray? 

O `FormArray` permite que a gente crie, de forma dinâmica, um conjunto de campos que representam um array de informações, sem precisar definir manualmente quantos campos existirão. 

No contexto desse formulário de cadastro, queremos permitir que a pessoa cadastre mais de um telefone e possa adicionar ou remover números conforme desejar. Isso torna o formulário mais flexível e adaptável às necessidades de quem está preenchendo. 

Como aplicamos no formulário 

1. No `FormGroup`, adicionamos o campo `telefones` como um FormArray vazio inicialmente: 

```ts 
telefones: this.fb.array([]), 
```

2. Criamos dois métodos no componente: 

- `adicionarTelefone()` – Adiciona um novo controle (`FormControl`) ao array `telefones`. Cada telefone deve ser preenchido e conter apenas números, então adicionamos os validadores `required` e `pattern`. 

```ts 
adicionarTelefone() { 
  this.telefones.push(this.fb.control('', [ 
    Validators.required,  
    Validators.pattern(/^\d+$/) 
  ])); 
} 
```

- `removerTelefone(index: number)` – Permite remover o telefone pelo índice na lista. 

```ts 
removerTelefone(index: number) { 
  this.telefones.removeAt(index); 
} 
```

3. No template, usamos a diretiva `*ngFor` para percorrer os controles do array `telefones` e exibir um campo `<input>` para cada um deles. Também adicionamos botões para adicionar ou remover telefones. 

Como ficou no template: 

```html 
<div formArrayName="telefones"> 
  <h3>Telefones</h3> 
  <div *ngFor="let telefone of telefones.controls; let i = index"> 
    <input [formControlName]="i" placeholder="Telefone" /> 
    <button type="button" (click)="removerTelefone(i)">Remover</button> 
    <div *ngIf="telefone.invalid && telefone.touched"> 
      <small *ngIf="telefone.errors?.['required']">Telefone é obrigatório.</small> 
      <small *ngIf="telefone.errors?.['pattern']">Digite apenas números.</small> 
    </div> 
  </div> 
  <button type="button" (click)="adicionarTelefone()">Adicionar Telefone</button> 
</div> 
```

Código completo do componente após as alterações com FormArray: 

```ts 
import { Component, OnInit } from '@angular/core'; 
import { 
  FormBuilder, 
  FormGroup, 
  Validators, 
  AbstractControl, 
  ValidationErrors, 
  FormArray, 
  ReactiveFormsModule, 
} from '@angular/forms'; 
import { CommonModule } from '@angular/common'; 
interface Usuario { 
  nome: string; 
  email: string; 
  tipoConta: string; 
  dataNascimento: string; 
  receberNewsletter: boolean; 
  telefones: string[]; 
} 
@Component({ 
  selector: 'app-form-reativo', 
  standalone: true, 
  imports: [CommonModule, ReactiveFormsModule], 
  templateUrl: './form-reativo.component.html', 
  styleUrls: ['./form-reativo.component.css'], 
}) 
export class FormReativoComponent implements OnInit { 
  formularioUsuario!: FormGroup; 
  constructor(private fb: FormBuilder) {} 
  ngOnInit() { 
    this.criarFormulario(); 
  } 
  private criarFormulario(): void { 
    this.formularioUsuario = this.fb.group({ 
      nome: ['', [Validators.required, Validators.minLength(3)]], 
      email: ['', [Validators.required, Validators.email]], 
      tipoConta: ['', Validators.required], 
      dataNascimento: ['', [Validators.required, FormReativoComponent.validarData]], 
      receberNewsletter: [false], 
      telefones: this.fb.array([]), 
    }); 
  } 
  static validarData(control: AbstractControl): ValidationErrors | null { 
    const data = new Date(control.value); 
    const hoje = new Date(); 
    return data > hoje ? { dataFutura: true } : null; 
  } 
  aoEnviar() { 
    const usuario: Usuario = this.formularioUsuario.value as Usuario; 
    alert( 
      `Formulário enviado (reativo): ${usuario.nome} teve seu cadastro bem sucedido!` 
    ); 
  } 
  get nome() { 
    return this.formularioUsuario.get('nome'); 
  } 
  get email() { 
    return this.formularioUsuario.get('email'); 
  } 
  get tipoConta() { 
    return this.formularioUsuario.get('tipoConta'); 
  } 
  get dataNascimento() { 
    return this.formularioUsuario.get('dataNascimento'); 
  } 
  get receberNewsletter() { 
    return this.formularioUsuario.get('receberNewsletter'); 
  } 
  get telefones() { 
    return this.formularioUsuario.get('telefones') as FormArray; 
  } 
  adicionarTelefone() { 
    this.telefones.push(this.fb.control('', [ 
      Validators.required, 
      Validators.pattern(/^\d+$/) 
    ])); 
  } 
  removerTelefone(index: number) { 
    this.telefones.removeAt(index); 
  } 
} 
```

Essa configuração resulta em campos de telefone com quantidade personalizável:  

Imagem de um formulário de cadastro no Angular com tema escuro. O formulário possui campos para nome, email, tipo de conta, data de nascimento e uma seção para telefones, que permite adicionar ou remover múltiplos números dinamicamente. Há também uma caixa de seleção para receber newsletter e um botão desabilitado de "Enviar"

Assim como os formulários Template-Driven, os  também tem suas forças e fraquezas:


Vantagens: 

  •  É mais fácil ter o controle sobre os estados e as validações 
  • Essa abordagem é muito mais fácil de testar 
  • Simplifica a criação de formulários complexos 

Desvantagens: 

  • Mais verboso se compararmos com o Template-Driven
  • Como é necessário conhecer as APIs reativas e saber usá-las, pode ser menos amigável para iniciantes 

Agora que você já conhece cada uma das abordagens de formulários no Angular, você pode estar se perguntando: qual eu devo escolher?  

Homem suando, indeciso entre "Template-driven forms" e "Reactive forms" no Angular.

Para te tirar dessa dúvida cruel, vamos explorar sobre como e onde usar cada tipo de formulário. 

Aplicações e usos dos diferentes tipos de formulários 

Nem todo formulário vai exigir o mesmo nível de controle ou ter a mesma complexidade. Então ao se perguntar sobre qual abordagem utilizar, repetiremos a clássica palavra que ouvimos sempre no mundo da tecnologia: depende… Aqui vamos decidir sobre a melhor abordagem com base no cenário da aplicação e do formulário que você precisa construir. 

Quando optamos por Template-driven forms? 

Vamos usar essa abordagem quando estamos em um cenário simples, com poucas regras de negócio. Como a configuração é feita no template HTML, o código fica mais declarativo e fácil de entender, sendo mais indicado em formulários básicos. 

Reactive forms:  

Se você já tem um cenário mais complexo, como um formulário com muitas validações, partes que são dinâmicas ou serão renderizadas de acordo com condicionais ou se precisa de testes mais elaborados, essa vai ser a abordagem ideal. Como a configuração será feita no código TypeScript, é possível ter um controle mais personalizado dos campos. 

Para te auxiliar na escolha da abordagem ideal, construímos um fluxograma para que faça sua própria comparação: 
 
 

Fluxograma de decisão para escolher entre eactive forms e template-driven forms ao criar formulários no Angular. O fluxo começa com a pergunta "Você vai criar um formulário no Angular?" e segue com ramificações baseadas na quantidade de campos, se os campos são dinâmicos, se há necessidade de validações complexas, testes, observação em tempo real ou lógica de negócio avançada. Ao final, recomenda o uso de reactive forms para casos mais complexos e template-driven forms para formulários simples.

Um detalhe importante: Testes 

Um detalhe que precisamos nos atentar ao escolher nossa abordagem é a testabilidade. A escrita e execução dos testes muda bastante de acordo com a abordagem escolhida. Vamos entender melhor como os testes funcionam em cada um dos casos: 

Testes em template-driven forms:  

Os testes em formulários template-driven podem ser mais complicados, isso porque a lógica fica diretamente no HTML e isso acaba dificultando no momento de simular e verificar estados de controle (como `valid`, `touched`, `dirty`, etc.). Assim, os testes acabam ficando mais superficiais. 

Testes em reactive forms:  

Essa abordagem é bem mais testável e o que permite isso é o controle no componente (arquivo TypeScript) via `FormGroup`. Você pode verificar e alterar o estado do formulário diretamente no componente. 

Considerações Finais 

Chegamos ao fim de mais uma jornada onde exploramos as duas principais abordagens para criar formulários no Angular: template-driven forms e reactive forms. Cada uma tem suas particularidades, vantagens e desvantagens, e nessa batalha, a escolha entre elas deve ser baseada no contexto do seu projeto.   

Aprendemos que os template-driven forms são ideais para formulários simples, com configuração rápida e menos código no TypeScript. São ótimos para iniciantes ou cenários onde a simplicidade é prioritária.   

Enquanto isso, os reactive forms oferecem maior controle, facilitam a criação de formulários complexos e dinâmicos. Eles também dão um show quando o assunto é testabilidade. São a escolha certa para aplicações com validações customizadas ou lógicas avançadas.  

Próximos passos 

Agora que você já conhece as diferenças, experimente ambas as abordagens em projetos reais para dominar ainda mais o universo de formulários no Angular! Para continuar se aprimorando, que tal dar uma olhada na Formação Angular 19

E agora, Dev? Qual abordagem você vai usar no seu próximo projeto? Compartilhe com a Alura nas redes sociais!  

Busque conhecimento 👽! Continue firme nos estudos e até a próxima :) 

Rafaela Petelin Silvério
Rafaela Petelin Silvério

Sou graduanda em Sistemas de Informação e Técnica em Desenvolvimento de Sistemas pela ETEC, atuo como Monitora de Fórum, principalmente na área de Front-end. Sou apaixonada por tecnologia e busco sempre aprender coisas novas. Meus hobbies favoritos são ler, estudar novos idiomas e assistir séries de comédia.

Veja outros artigos sobre Front-end