Publicado em

- 5 minutos de leitura

Criando Formulários Dinâmicos no Angular

img of 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.

Imagem com o texto: Curso Formulários com Angular e com a logo do Angular em cor roxa. Logo abaixo existe um botão com o texto "Eu quero!"

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

Author's photo
Henrique Custódia Arquiteto Frontend, entusiasta Angular, cat lover, criador de conteúdo e fundador da Code Dimension!