Skip to main content

Angular Forms com Signals | Formulários | Angular

  • Angular Forms com Signals | Formulários | Angular

O Signal Forms é uma nova abordagem do Angular para criar formulários usando Signals em vez de depender diretamente de FormGroup, FormControl, valueChanges e fluxo baseado em Observable.

Ele ainda é tratado pela documentação oficial como experimental, então a API pode mudar em versões futuras. Use com atenção em produção. (Angular)

Ideia principal

Nos Reactive Forms tradicionais, normalmente você cria a estrutura do formulário assim:

form = new FormGroup({
name: new FormControl(''),
email: new FormControl('')
});

Com Signal Forms, você começa pelo modelo de dados:

user = signal({
name: '',
email: ''
});

Depois, cria o formulário em cima desse model:

userForm = form(this.user);

Ou seja:

Reactive FormsSignal Forms
Começa pelo FormGroupComeça pelo signal()
Usa FormControlUsa campos derivados do model
Usa valueChangesUsa leitura reativa via Signals
Muito baseado em RxJSMais integrado com Signals
API estávelAPI experimental

Instalação / importação

A API fica em:

import { form } from '@angular/forms/signals';

Exemplo:

import { Component, signal } from '@angular/core';
import { form } from '@angular/forms/signals';

@Component({
selector: 'app-user-form',
templateUrl: './user-form.component.html',
})
export class UserFormComponent {
user = signal({
name: '',
email: '',
});

userForm = form(this.user);
}

Exemplo básico

Component

import { Component, signal } from '@angular/core';
import { form } from '@angular/forms/signals';

@Component({
selector: 'app-user-form',
templateUrl: './user-form.component.html',
})
export class UserFormComponent {
user = signal({
name: '',
email: '',
});

userForm = form(this.user);

submit() {
console.log(this.user());
}
}

Template

<form (ngSubmit)="submit()">
<label>
Nome
<input [field]="userForm.name" />
</label>

<label>
Email
<input [field]="userForm.email" />
</label>

<button type="submit">
Salvar
</button>
</form>

O ponto importante é este:

<input [field]="userForm.name" />

O [field] conecta o input ao campo do formulário baseado em Signals.

Como acessar o valor

Como o model é um signal, você lê o valor assim:

console.log(this.user());

Exemplo de saída:

{
name: 'Adolfo',
email: 'adolfo@email.com'
}

Não precisa fazer:

this.form.value

Nem assinar:

this.form.valueChanges.subscribe(...)

Como alterar valor manualmente

Como o estado é um signal, você pode usar set:

this.user.set({
name: 'Adolfo',
email: 'adolfo@email.com',
});

Ou update:

this.user.update(current => ({
...current,
name: 'Novo nome',
}));

Validação

O Signal Forms trabalha com validação baseada em schema/funções.

Exemplo conceitual:

import { form, required, email } from '@angular/forms/signals';

userForm = form(this.user, path => {
required(path.name);
required(path.email);
email(path.email);
});

A ideia é declarar as regras em cima dos campos do model.

Exemplo:

user = signal({
name: '',
email: '',
});

userForm = form(this.user, path => {
required(path.name, {
message: 'Nome é obrigatório',
});

required(path.email, {
message: 'Email é obrigatório',
});

email(path.email, {
message: 'Email inválido',
});
});

Exibindo erro no template

Exemplo:

<label>
Nome
<input [field]="userForm.name" />
</label>

@if (userForm.name().invalid()) {
<p>Nome inválido</p>
}

Com mensagem:

@if (userForm.name().errors().length > 0) {
<p>
{{ userForm.name().errors()[0].message }}
</p>
}

A leitura pode parecer estranha no começo porque você está lidando com Signals:

userForm.name().invalid()
userForm.name().errors()
userForm.name().touched()
userForm.name().dirty()

Estados comuns do campo

Em um campo do formulário, você normalmente verifica:

userForm.name().value()
userForm.name().valid()
userForm.name().invalid()
userForm.name().touched()
userForm.name().dirty()
userForm.name().errors()

Exemplo:

@if (userForm.email().touched() && userForm.email().invalid()) {
<p>Email inválido</p>
}

Formulário com objeto mais complexo

profile = signal({
name: '',
email: '',
address: {
street: '',
city: '',
},
});

profileForm = form(this.profile);

Template:

<input [field]="profileForm.name" />
<input [field]="profileForm.email" />

<input [field]="profileForm.address.street" />
<input [field]="profileForm.address.city" />

O acesso segue a estrutura do objeto.

Formulário com array

Exemplo de model:

user = signal({
name: '',
skills: ['Angular', 'TypeScript'],
});

userForm = form(this.user);

Template conceitual:

@for (skill of userForm.skills; track $index) {
<input [field]="skill" />
}

Para arrays dinâmicos, a API pode mudar bastante, então é uma das partes onde eu teria mais cautela em produção.

Submit

Exemplo simples:

submit() {
if (this.userForm().invalid()) {
return;
}

const payload = this.user();

console.log(payload);
}

Template:

<form (ngSubmit)="submit()">
<input [field]="userForm.name" />
<input [field]="userForm.email" />

<button type="submit">
Salvar
</button>
</form>

Comparando com Reactive Forms

Reactive Forms

form = new FormGroup({
name: new FormControl('', Validators.required),
email: new FormControl('', [
Validators.required,
Validators.email,
]),
});
<input [formControl]="form.controls.name" />
<input [formControl]="form.controls.email" />

Signal Forms

user = signal({
name: '',
email: '',
});

userForm = form(this.user, path => {
required(path.name);
required(path.email);
email(path.email);
});
<input [field]="userForm.name" />
<input [field]="userForm.email" />

Quando usar Signal Forms

Use quando:

  • você já está usando Angular com Signals;
  • quer evitar valueChanges para casos simples;
  • quer um formulário mais próximo do model;
  • quer melhor integração com componentes OnPush;
  • está testando Angular mais moderno;
  • o projeto tolera API experimental.

Quando evitar

Evite quando:

  • o projeto é crítico e precisa de API 100% estável;
  • o time já domina Reactive Forms;
  • o formulário é muito complexo;
  • há muitos componentes customizados antigos com ControlValueAccessor;
  • você depende fortemente de bibliotecas que esperam FormGroup/FormControl.

Pegadinhas

1. Ainda é experimental

A própria documentação oficial marca Signal Forms como experimental. A API pode mudar em versões futuras. (Angular)

2. Não é só “Reactive Forms com Signal”

Signal Forms não é apenas um FormControl com Signal por cima. A mentalidade muda:

// Antes
form.value

// Agora
modelSignal()

Você pensa mais no estado do model do que na estrutura do formulário.

3. Menos RxJS, mas não significa zero RxJS

Você reduz o uso de valueChanges, mas ainda pode precisar de RxJS em cenários como:

  • debounce;
  • chamada HTTP;
  • autocomplete;
  • validação assíncrona;
  • integração com APIs existentes.

4. Cuidado com exemplos antigos

Como a API é recente, muitos exemplos de blog podem ficar defasados. Priorize a documentação oficial do Angular.

Exemplo mais completo

import { Component, signal } from '@angular/core';
import {
form,
required,
email,
minLength,
} from '@angular/forms/signals';

@Component({
selector: 'app-register-form',
templateUrl: './register-form.component.html',
})
export class RegisterFormComponent {
user = signal({
name: '',
email: '',
password: '',
});

userForm = form(this.user, path => {
required(path.name, {
message: 'Nome é obrigatório',
});

required(path.email, {
message: 'Email é obrigatório',
});

email(path.email, {
message: 'Email inválido',
});

required(path.password, {
message: 'Senha é obrigatória',
});

minLength(path.password, 6, {
message: 'Senha deve ter no mínimo 6 caracteres',
});
});

submit() {
if (this.userForm().invalid()) {
return;
}

const payload = this.user();

console.log('Enviando:', payload);
}
}

Template:

<form (ngSubmit)="submit()">
<div>
<label>Nome</label>
<input [field]="userForm.name" />

@if (userForm.name().touched() && userForm.name().invalid()) {
<p>{{ userForm.name().errors()[0].message }}</p>
}
</div>

<div>
<label>Email</label>
<input [field]="userForm.email" />

@if (userForm.email().touched() && userForm.email().invalid()) {
<p>{{ userForm.email().errors()[0].message }}</p>
}
</div>

<div>
<label>Senha</label>
<input type="password" [field]="userForm.password" />

@if (userForm.password().touched() && userForm.password().invalid()) {
<p>{{ userForm.password().errors()[0].message }}</p>
}
</div>

<button type="submit" [disabled]="userForm().invalid()">
Criar conta
</button>
</form>

Resumo direto

Signal Forms é a tentativa do Angular de modernizar formulários usando Signals.

A ideia é:

model = signal({...});
formModel = form(model);

E no HTML:

<input [field]="formModel.name" />

Para projeto novo, vale estudar. Para produção crítica, ainda exige cautela porque a API é experimental.