Introdução
Essenciais

Forms with signals

Signal Forms é construído em cima dos signals do Angular para fornecer uma maneira reativa e type-safe de gerenciar o estado de formulários.

Signal Forms gerencia o estado de formulários usando signals do Angular para fornecer sincronização automática entre seu modelo de dados e a UI.

Este guia apresenta os conceitos principais para criar formulários com Signal Forms. Veja como funciona:

Criando seu primeiro formulário

1. Crie um modelo de formulário

Todo formulário começa criando um signal que armazena o modelo de dados do seu formulário:

interface LoginData {  email: string;  password: string;}const loginModel = signal<LoginData>({  email: '',  password: '',});

2. Passe o modelo de formulário para form()

Em seguida, você passa seu modelo de formulário para a função form() para criar uma field tree - uma estrutura de objeto que espelha a forma do seu modelo, permitindo que você acesse campos com notação de ponto:

form(loginModel);// Access fields directly by property nameloginForm.emailloginForm.password

3. Vincule inputs com a directive [field]

Em seguida, você vincula seus inputs HTML ao formulário usando a directive [field], que cria um binding bidirecional entre eles:

<input type="email" [field]="loginForm.email" /><input type="password" [field]="loginForm.password" />

Como resultado, mudanças do usuário (como digitar no campo) atualizam automaticamente o formulário, e quaisquer mudanças programáticas também atualizam o valor exibido:

// Update the value programmaticallyloginForm.email().value.set('alice@wonderland.com');// The model signal is also updatedconsole.log(loginModel().email); // 'alice@wonderland.com'

NOTA: A directive [field] também sincroniza o estado do campo para atributos como required, disabled e readonly quando apropriado.

4. Leia valores de campos do formulário com value()

Você pode acessar o estado do campo chamando o campo como uma função. Isso retorna um objeto FieldState contendo signals reativos para o valor do campo, status de validação e estado de interação:

loginForm.email() // Returns FieldState with value(), valid(), touched(), etc.

Para ler o valor atual do campo, acesse o signal value():

<!-- Render form value that updates automatically as user types --><p>Email: {{ loginForm.email().value() }}</p>
// Get the current valueconst currentEmail = loginForm.email().value();

Aqui está um exemplo completo:

app.ts

import {Component, signal} from '@angular/core';import {form, Field} from '@angular/forms/signals';interface LoginData {  email: string;  password: string;}@Component({  selector: 'app-root',  templateUrl: 'app.html',  styleUrl: 'app.css',  imports: [Field],})export class App {  loginModel = signal<LoginData>({    email: '',    password: '',  });  loginForm = form(this.loginModel);}

app.html

<form>  <label>    Email:    <input type="email" [field]="loginForm.email" />  </label>  <label>    Password:    <input type="password" [field]="loginForm.password" />  </label>  <p>Hello {{ loginForm.email().value() }}!</p>  <p>Password length: {{ loginForm.password().value().length }}</p></form>

app.css

form {  display: flex;  flex-direction: column;  gap: 1rem;  max-width: 400px;  padding: 1rem;  font-family: Inter, system-ui, -apple-system, sans-serif;}label {  display: flex;  flex-direction: column;  gap: 0.25rem;}input {  padding: 0.5rem;  border: 1px solid #ccc;  border-radius: 4px;  font-size: 1rem;  font-family: inherit;}p {  margin: 0.5rem 0;  color: #666;}

Uso básico

A directive [field] funciona com todos os tipos de input HTML padrão. Aqui estão os padrões mais comuns:

Inputs de texto

Inputs de texto funcionam com vários atributos type e textareas:

<!-- Text and email --><input type="text" [field]="form.name" /><input type="email" [field]="form.email" />

Números

Inputs de número convertem automaticamente entre strings e números:

<!-- Number - automatically converts to number type --><input type="number" [field]="form.age" />

Data e hora

Inputs de data armazenam valores como strings YYYY-MM-DD, e inputs de hora usam o formato HH:mm:

<!-- Date and time - stores as ISO format strings --><input type="date" [field]="form.eventDate" /><input type="time" [field]="form.eventTime" />

Se você precisar converter strings de data para objetos Date, você pode fazer isso passando o valor do campo para Date():

const dateObject = new Date(form.eventDate().value());

Texto multilinha

Textareas funcionam da mesma forma que inputs de texto:

<!-- Textarea --><textarea [field]="form.message" rows="4"></textarea>

Checkboxes

Checkboxes vinculam a valores booleanos:

<!-- Single checkbox --><label>  <input type="checkbox" [field]="form.agreeToTerms" />  I agree to the terms</label>

Múltiplas checkboxes

Para múltiplas opções, crie um field booleano separado para cada:

<label>  <input type="checkbox" [field]="form.emailNotifications" />  Email notifications</label><label>  <input type="checkbox" [field]="form.smsNotifications" />  SMS notifications</label>

Radio buttons

Radio buttons funcionam de forma similar a checkboxes. Desde que os radio buttons usem o mesmo valor [field], Signal Forms automaticamente vinculará o mesmo atributo name a todos eles:

<label>  <input type="radio" value="free" [field]="form.plan" />  Free</label><label>  <input type="radio" value="premium" [field]="form.plan" />  Premium</label>

Quando um usuário seleciona um radio button, o field do formulário armazena o valor do atributo value daquele radio button. Por exemplo, selecionar "Premium" define form.plan().value() como "premium".

Select dropdowns

Elementos select funcionam com opções estáticas e dinâmicas:

<!-- Static options --><select [field]="form.country">  <option value="">Select a country</option>  <option value="us">United States</option>  <option value="ca">Canada</option></select><!-- Dynamic options with @for --><select [field]="form.productId">  <option value="">Select a product</option>  @for (product of products; track product.id) {    <option [value]="product.id">{{ product.name }}</option>  }</select>

NOTA: Select múltiplo (<select multiple>) não é suportado pela directive [field] neste momento.

Validação e estado

Signal Forms fornece validators integrados que você pode aplicar aos campos do seu formulário. Para adicionar validação, passe uma função schema como segundo argumento para form(). Esta função recebe um parâmetro FieldPath que permite referenciar os campos no modelo do seu formulário:

const loginForm = form(loginModel, (fieldPath) => {  required(fieldPath.email);  email(fieldPath.email);});

NOTA: FieldPath apenas espelha a forma dos seus dados e não permite acessar value ou qualquer outro estado.

Validators comuns incluem:

Você também pode personalizar mensagens de erro passando um objeto de opções como segundo argumento para o validator:

required(p.email, { message: 'Email is required' });email(p.email, { message: 'Please enter a valid email address' });

Cada campo do formulário expõe seu estado de validação através de signals. Por exemplo, você pode verificar field().valid() para ver se a validação passa, field().touched() para ver se o usuário interagiu com ele, e field().errors() para obter a lista de erros de validação.

Aqui está um exemplo completo:

app.ts

import {Component, signal} from '@angular/core';import {form, Field, required, email, submit} from '@angular/forms/signals';interface LoginData {  email: string;  password: string;}@Component({  selector: 'app-root',  templateUrl: 'app.html',  styleUrl: 'app.css',  imports: [Field],})export class App {  loginModel = signal<LoginData>({    email: '',    password: '',  });  loginForm = form(this.loginModel, (p) => {    required(p.email, {message: 'Email is required'});    email(p.email, {message: 'Enter a valid email address'});    required(p.password, {message: 'Password is required'});  });  onSubmit(event: Event) {    event.preventDefault();    submit(this.loginForm, async () => {      // Perform login logic here      const credentials = this.loginModel();      console.log('Logging in with:', credentials);      // e.g., await this.authService.login(credentials);    });  }}

app.html

<form (submit)="onSubmit($event)">  <div>    <label>      Email:      <input type="email" [field]="loginForm.email" />    </label>    @if (loginForm.email().touched() && loginForm.email().invalid()) {      <ul class="error-list">        @for (error of loginForm.email().errors(); track $index) {          <li>{{ error.message }}</li>        }      </ul>    }  </div>  <div>    <label>      Password:      <input type="password" [field]="loginForm.password" />    </label>    @if (loginForm.password().touched() && loginForm.password().invalid()) {      <div class="error">        @for (error of loginForm.password().errors(); track $index) {          <p>{{ error.message }}</p>        }      </div>    }  </div>  <button type="submit">Log In</button></form>

app.css

form {  display: flex;  flex-direction: column;  gap: 1rem;  max-width: 400px;  padding: 1rem;  font-family:    Inter,    system-ui,    -apple-system,    sans-serif;}div {  display: flex;  flex-direction: column;  gap: 0.25rem;}label {  display: flex;  flex-direction: column;  gap: 0.25rem;  font-weight: 500;}input {  padding: 0.5rem;  border: 1px solid #ccc;  border-radius: 4px;  font-size: 1rem;  font-family: inherit;}input:focus {  outline: none;  border-color: #4285f4;}button {  padding: 0.75rem 1.5rem;  background-color: #4285f4;  color: white;  border: none;  border-radius: 4px;  font-size: 1rem;  font-family: inherit;  cursor: pointer;  transition: background-color 0.2s;}button:hover {  background-color: #357ae8;}button:active {  background-color: #2a65c8;}.error-list {  color: red;  font-size: 0.875rem;  margin: 0.25rem 0 0 0;  padding-left: 0;  list-style-position: inside;}.error-list p {  margin: 0;}

Field State Signals

Cada field() fornece estes signals de estado:

Estado Descrição
valid() Retorna true se o campo passa em todas as regras de validação
touched() Retorna true se o usuário focou e desfocou o campo
dirty() Retorna true se o usuário mudou o valor
disabled() Retorna true se o campo está desabilitado
pending() Retorna true se a validação assíncrona está em progresso
errors() Retorna um array de erros de validação com propriedades kind e message

DICA: Mostre erros apenas após field().touched() ser verdadeiro para evitar exibir mensagens de validação antes que o usuário tenha interagido com um campo.