Publicado em
- 5 minutos de leitura
Criando Formulários Dinâmicos no Angular
Criar formulários em aplicações web é uma tarefa fundamental, mas que pode se tornar repetitiva e cansativa rapidamente.
Quando lidamos com sistemas complexos, escrever o HTML de cada campo de forma manual deixa o código longo e difícil de manter.
É aqui que entram os formulários dinâmicos, uma técnica poderosa para gerar interfaces baseadas em dados.
Neste artigo, vamos aprender como construir um formulário no Angular que renderiza seus campos automaticamente a partir de um objeto JavaScript ou JSON.
O que é
Um formulário dinâmico é um componente que não possui seus campos fixos ou “chumbados” no código HTML.
Em vez disso, ele recebe uma estrutura de dados, como um array de configurações, e constrói a interface visual em tempo de execução.
Isso significa que o próprio componente decide se vai desenhar um campo de texto, de e-mail ou uma lista de seleção com base no que foi configurado no objeto.
Para que serve
Essa abordagem serve para cenários onde a estrutura dos dados que precisamos coletar muda com frequência ou depende de regras de negócio específicas.
É muito comum utilizar formulários dinâmicos em painéis administrativos, criadores de pesquisas de satisfação ou sistemas de inspeção.
Com ele, você pode buscar a configuração de um formulário diretamente do seu backend via API.
Dessa forma, a interface é exibida para o usuário sem que você precise alterar o código fonte da aplicação para adicionar um novo campo.
Vantagens
A principal vantagem é a drástica redução da repetição de código no seu projeto.
Você cria a lógica visual apenas uma vez e a reutiliza para dezenas de formulários diferentes ao longo do sistema.
A manutenção se torna muito mais simples, pois a adição ou correção de um tipo de campo é feita em um único componente.
Além disso, a integração com APIs fica mais natural e escalável.
Como implementar
Para implementar nosso formulário dinâmico, vamos utilizar as ferramentas mais modernas do Angular, como a API de Signals e a nova Control Flow Syntax.
O primeiro passo é definir a estrutura de dados que vai representar o nosso formulário através de interfaces TypeScript.
export enum FieldType {
Text = 'text',
Email = 'email',
Select = 'select'
}
export interface DynamicFormSchema {
submitLabel: string
fields: DynamicFormFieldSchema[]
}
export interface DynamicFormFieldSchema<T = unknown> {
name: string
label: string
type: FieldType
required: boolean
initialValue?: T
placeholder?: string
options?: { value: string; label: string }[]
}
Com os modelos bem definidos, o próximo passo é criar a lógica do nosso componente principal, o DynamicForm.
Ele vai receber o esquema através da nova função input() e gerar um grupo de formulário reativo de forma automatizada.
Utilizaremos a função computed() para construir e manter os controles do formulário sempre sincronizados com o esquema recebido.
import { ChangeDetectionStrategy, Component, computed, inject, input, output } from '@angular/core'
import {
FormControl,
FormGroup,
NonNullableFormBuilder,
ReactiveFormsModule,
Validators
} from '@angular/forms'
import { CommonModule } from '@angular/common'
@Component({
selector: 'app-dynamic-form',
imports: [ReactiveFormsModule, CommonModule],
templateUrl: './dynamic-form.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DynamicForm {
private formBuilder = inject(NonNullableFormBuilder)
schema = input.required<DynamicFormSchema>()
submit = output<{ [key: string]: unknown }>()
protected fieldType = FieldType
protected submitLabel = computed(() => this.schema().submitLabel)
protected formControls = computed(() => {
return this.schema().fields.reduce<Record<string, FormControl>>((controls, schema) => {
controls[schema.name] = this.createFormControl(schema)
return controls
}, {})
})
protected form = computed(() => new FormGroup(this.formControls()))
protected onSubmit() {
this.submit.emit(this.form().value)
}
private createFormControl(schema: DynamicFormFieldSchema) {
const control = this.formBuilder.control(schema.initialValue)
if (schema.required) {
control.addValidators([Validators.required])
}
if (schema.type === FieldType.Email) {
control.addValidators([Validators.email])
}
return control
}
}
Agora precisamos construir o template HTML que vai reagir a essa lógica.
Vamos iterar sobre a lista de campos usando o bloco @for nativo do Angular.
Dentro do laço de repetição, utilizaremos o @switch para renderizar o elemento de entrada correto (input ou select).
<form [formGroup]="form()">
@for (item of schema().fields; track item.name) { @switch (item.type) { @case (fieldType.Text) {
<fieldset class="fieldset">
<legend class="fieldset-legend">{{ item.label }}</legend>
<input
type="text"
class="input w-full"
[placeholder]="item.placeholder ?? item.label"
[formControlName]="item.name"
/>
</fieldset>
} @case (fieldType.Email) {
<fieldset class="fieldset">
<legend class="fieldset-legend">{{ item.label }}</legend>
<input
type="email"
class="input w-full"
[placeholder]="item.placeholder ?? item.label"
[formControlName]="item.name"
/>
</fieldset>
} @case (fieldType.Select) {
<fieldset class="fieldset">
<legend class="fieldset-legend">{{ item.label }}</legend>
<select class="select w-full" [formControlName]="item.name">
<option value="">{{ item.placeholder }}</option>
@for (option of item.options; track option.value) {
<option [value]="option.value">{{ option.label }}</option>
}
</select>
</fieldset>
} } }
<button
type="submit"
class="btn btn-primary w-full mt-4"
[disabled]="form().invalid"
(click)="onSubmit()"
>
{{ submitLabel() }}
</button>
</form>
Por fim, basta utilizarmos o nosso componente passando um objeto estruturado.
Veja como fica a chamada de uso em um componente pai.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core'
import {
DynamicForm,
DynamicFormFieldSchema,
DynamicFormSchema,
FieldType
} from './dynamic-form/dynamic-form'
@Component({
selector: 'app-create-dynamic-form',
imports: [DynamicForm],
template: `<app-dynamic-form [schema]="schema()" (submit)="onSubmit($event)"></app-dynamic-form>`,
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
class: 'w-full max-w-md'
}
})
export class CreateDynamicForm {
schema = signal<DynamicFormSchema>({
submitLabel: 'Salvar',
fields: [
{
name: 'name',
label: 'Name',
type: FieldType.Text,
required: true,
initialValue: '',
placeholder: 'Enter your name'
} as DynamicFormFieldSchema<string>,
{
name: 'email',
label: 'Email',
type: FieldType.Email,
required: true,
initialValue: '',
placeholder: 'Enter your email'
} as DynamicFormFieldSchema<string>,
{
name: 'role',
label: 'Role',
type: FieldType.Select,
required: true,
initialValue: '',
placeholder: 'Selecione seu cargo',
options: [
{ value: 'admin', label: 'Administrador' },
{ value: 'user', label: 'Usuário' }
]
} as DynamicFormFieldSchema<string>
]
})
onSubmit($event: { [key: string]: unknown }) {
console.log($event)
}
}
Conclusão
Criar formulários dinâmicos no Angular se tornou uma tarefa incrivelmente elegante e performática com o uso combinado de Signals e da nova Control Flow Syntax.
Ao adotar essa estratégia no seu projeto, você ganha flexibilidade instantânea e mantém o código muito mais enxuto e organizado.
A base apresentada neste artigo é sólida e permite que você expanda as funcionalidades facilmente no dia a dia.
Agora você pode adicionar novos tipos de campos, validações customizadas avançadas e até estilizações específicas sem ter o medo de quebrar o restante da sua aplicação.
Assine nossa Newsletter
Receba novos posts como esse na sua caixa de e-mail!
Sobre o autor