Publicado em
- 7 minutos de leitura
Angular 21.2: Novidades em Signal Forms
O ecossistema do Angular está passando por uma transformação significativa com a introdução e o aprimoramento contínuo dos Signal Forms.
Desde a versão 21.0 até a recente 21.2, o framework tem recebido atualizações robustas que visam modernizar a criação de formulários, oferecendo uma alternativa moderna aos já estabelecidos Reactive Forms.
Essas novidades ajudam na transição de projetos complexos e facilitam a adoção de APIs reativas de forma incremental.
Este artigo detalha os novos recursos, servindo como um guia prático sobre opções de debounce, controle de foco, a nova API de submissão, a diretiva estrutural e estratégias de interoperabilidade.
Opção blur na função de debounce de Signal Forms
A função debounce no Signal Forms agora aceita a opção 'blur' diretamente na definição do esquema do formulário.
Isso significa que você pode restringir as atualizações do modelo de dados para que ocorram apenas quando o usuário sai do campo de input, perdendo o foco no elemento.
Essa novidade atua como uma excelente alternativa ao uso de atrasos baseados em tempo, como debounce(field, 300).
O principal benefício é a otimização de performance, pois o modelo não é atualizado a cada tecla pressionada, o que é ideal para formulários complexos.
Além disso, quando combinado com validações assíncronas (validateAsync), essa abordagem evita o “spam” de chamadas à sua API, disparando a validação apenas no momento em que o usuário termina a digitação e sai do campo.
A submissão do formulário também age como um “blur” final, garantindo que qualquer valor pendente seja sincronizado e atualizado antes do envio.
import { Component, ChangeDetectionStrategy, signal } from '@angular/core'
import { form, debounce } from '@angular/forms/signals'
@Component({
selector: 'app-user-form',
template: `
<form>
<input type="text" [formField]="userForm.username" />
</form>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserFormComponent {
model = signal({ username: '' })
userForm = form(this.model, (schema) => {
debounce(schema.username, 'blur')
})
}
Nova função provideSignalFormsConfig
Para facilitar a padronização visual, o Angular introduziu o método provideSignalFormsConfig.
Essa função permite configurar globalmente como as classes CSS são aplicadas aos controles do Signal Forms com base no estado atual de cada campo.
Se você está migrando projetos e deseja manter o mesmo comportamento visual anterior, o Angular fornece a constante NG_STATUS_CLASSES.
Ao usar essa constante, o framework reaplica automaticamente as classes tradicionais, como ng-dirty, ng-touched, ng-valid e ng-invalid.
Você também tem total liberdade para definir lógicas customizadas e injetar classes específicas apenas quando um campo estiver tocado e inválido simultaneamente, por exemplo.
import { ApplicationConfig } from '@angular/core'
import { provideSignalFormsConfig } from '@angular/forms/signals'
import { NG_STATUS_CLASSES } from '@angular/forms/signals/compat'
export const appConfig: ApplicationConfig = {
providers: [
provideSignalFormsConfig({
classes: NG_STATUS_CLASSES
})
]
}
Como usar a função focusBoundControl
A partir da versão 21.1.0, gerenciar o foco de elementos ficou muito mais simples com o método focusBoundControl().
Este método encontra automaticamente o controle de interface da web associado a um campo no DOM e aplica o foco nele.
Isso elimina a necessidade de usar marcações de referência no template e consultas manuais no DOM no código TypeScript.
A inteligência da função garante que o primeiro elemento na ordem do DOM seja focado, ou que o método customizado de foco de um componente próprio seja acionado.
É um recurso incrível para melhorar a experiência do usuário, permitindo focar automaticamente no primeiro campo com erro após uma tentativa de submissão inválida.
import { Component, ChangeDetectionStrategy, signal } from '@angular/core'
import { form, FormField } from '@angular/forms/signals'
@Component({
selector: 'app-login',
imports: [FormField],
template: `
<form>
<input type="email" [formField]="loginForm.email" />
<button type="button" (click)="focusEmail()">Focar E-mail</button>
</form>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class LoginComponent {
loginModel = signal({ email: '' })
loginForm = form(this.loginModel)
focusEmail() {
this.loginForm.email().focusBoundControl()
}
}
A Nova API de Submissão
A nova API de submissão fornece uma maneira muito mais estruturada para lidar com o envio de formulários, gerenciando nativamente os estados válidos e inválidos.
Você pode implementar uma ação de submissão usando a opção submission diretamente dentro da função form().
A propriedade action é chamada quando um formulário válido é submetido, recebendo a instância do formulário para realizar operações, como chamadas de API.
Essa ação marca automaticamente todos os campos do formulário como tocados, removendo a necessidade de iterações manuais.
Para lidar com falhas de envio, a API oferece a propriedade onInvalid, que é perfeita para aplicar lógicas como focar no primeiro campo com erro usando o focusBoundControl.
Além disso, a API de submissão introduz a propriedade ignoreValidators, que oferece controle granular sobre como os validadores (especialmente os assíncronos) afetam o processo de envio.
Ela suporta três valores:
-
'pending'(padrão): Permite o envio do formulário mesmo se os validadores assíncronos ainda estiverem em execução. -
'none': Bloqueia o envio completamente até que todas as validações, incluindo as assíncronas, sejam finalizadas. -
'all': Ignora completamente o estado de validação, permitindo que o formulário seja enviado independentemente de estar válido ou não, o que é ideal para funcionalidades como “salvar rascunho”.
import { Component, ChangeDetectionStrategy, signal } from '@angular/core'
import { form } from '@angular/forms/signals'
@Component({
selector: 'app-submit-form',
template: ` <form></form> `,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SubmitFormComponent {
modelo = signal({ nome: '' })
meuFormulario = form(this.modelo, () => {}, {
submission: {
action: async (f) => {
const valores = f().value()
// Lógica de envio para a API aqui
return undefined
},
onInvalid: (f) => {
f().errorSummary()[0]?.fieldTree().focusBoundControl()
},
ignoreValidators: 'none' // Bloqueia o envio até as validações finalizarem
}
})
}
A Diretiva formRoot
Para acionar a lógica de envio definida na API de submissão sem precisar criar manipuladores de eventos manuais, o Angular introduziu a diretiva formRoot.
Ao aplicar a diretiva [formRoot]="form" no elemento raiz <form>, o Angular gerencia a submissão automaticamente quando o usuário clica em um botão de submit ou pressiona “Enter” em um input.
A diretiva formRoot também injeta o atributo novalidate ao formulário, desabilitando a validação padrão do próprio navegador para que o Angular assuma o controle total.
É necessário importar o FormRoot do pacote @angular/forms/signals nos imports do seu componente para utilizá-la corretamente.
import { Component, ChangeDetectionStrategy, signal } from '@angular/core'
import { form, FormRoot, FormField } from '@angular/forms/signals'
@Component({
selector: 'app-root-form',
imports: [FormRoot, FormField],
template: `
<form [formRoot]="meuFormulario">
<input type="text" [formField]="meuFormulario.nome" />
<button type="submit">Enviar</button>
</form>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RootFormComponent {
modelo = signal({ nome: '' })
meuFormulario = form(this.modelo, () => {}, {
submission: {
action: async (f) => {
console.log('Formulário válido enviado', f().value())
return undefined
}
}
})
}
Como usar SignalFormControl para integrar Reactive Forms com Signal Forms
O SignalFormControl, introduzido no Angular 21.2, atua como uma ponte vitalícia para usar validações baseadas em Signals dentro de uma infraestrutura tradicional de Reactive Forms.
Ele é consumido no template e no código exatamente como um FormControl comum dentro de um FormGroup, mas sua validação é definida por um esquema nativo do Signal Forms.
Isso permite uma migração gradual em projetos extensos, modernizando o código campo a campo, sem a necessidade de reescrever formulários inteiros de uma só vez.
Do ponto de vista do framework, o estado de erro e o valor final do SignalFormControl se propagam de forma natural para cima, atingindo o FormGroup pai.
import { Component, ChangeDetectionStrategy, inject } from '@angular/core'
import { NonNullableFormBuilder, ReactiveFormsModule } from '@angular/forms'
import { SignalFormControl, required } from '@angular/forms/signals'
@Component({
selector: 'app-passenger',
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="passengerGroup">
<input formControlName="firstName" />
<fieldset>
<input formControlName="phoneNumber" />
</fieldset>
</form>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PassengerComponent {
private formBuilder = inject(NonNullableFormBuilder)
phoneNumber = new SignalFormControl('', (path) => {
required(path)
})
passengerGroup = this.formBuilder.group({
firstName: '',
phoneNumber: this.phoneNumber
})
}
Como usar compatForm para integrar Signal Forms dentro de Reactive Forms
Enquanto o SignalFormControl faz o caminho inverso, o helper compatForm permite inserir pedaços estruturados de Reactive Forms diretamente dentro de um Signal Form raiz.
Para realizar essa integração, você simplesmente adiciona um FormGroup já existente como uma propriedade dentro do modelo de dados (signal) do seu formulário novo.
Em seguida, em vez de inicializar o esquema com a função padrão form(), você utiliza a função compatForm() passando esse modelo misto.
Nesse cenário, a reatividade do formulário raiz e as validações respeitam integralmente o estado interno do FormGroup aninhado, propagando o estado de erro para cima de forma automática.
import { Component, ChangeDetectionStrategy, signal, inject } from '@angular/core'
import { NonNullableFormBuilder, ReactiveFormsModule } from '@angular/forms'
import { compatForm, FormField, required } from '@angular/forms/signals'
@Component({
selector: 'app-checkin',
imports: [ReactiveFormsModule, FormField],
template: `
<form>
<input [formField]="checkinForm.ticketId" />
<fieldset [formGroup]="passengerGroup">
<input formControlName="firstName" />
</fieldset>
</form>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CheckinComponent {
private formBuilder = inject(NonNullableFormBuilder)
passengerGroup = this.formBuilder.group({
firstName: ''
})
checkinFormModel = signal({
ticketId: '',
passenger: this.passengerGroup
})
checkinForm = compatForm(this.checkinFormModel, (path) => {
required(path.ticketId)
})
}
Conclusão
Com as novas e constantes implementações, o Angular Signal Forms consolida-se como uma alternativa moderna e incrivelmente flexível aos já estabelecidos Reactive Forms.
As melhorias de foco programático, opções de validação assíncrona eficientes sob demanda e a robusta API de submissão unida à diretiva formRoot tornam a experiência de desenvolvimento declarativa e menos repetitiva.
O grande diferencial destas atualizações recentes é o cuidado evidente com o ciclo de vida das aplicações reais de nível empresarial, algo viabilizado pelas potentes pontes de interoperabilidade.
Ao permitir que Signal Forms e Reactive Forms coexistam perfeitamente através das ferramentas SignalFormControl e compatForm, o Angular garante refatorações graduais e seguras, impulsionando um desenvolvimento à prova do futuro sem ignorar bases de código já estabelecidas.
Assine nossa Newsletter
Receba novos posts como esse na sua caixa de e-mail!
Sobre o autor