Introduction
Essentials

Forms with signals

IMPORTANT: Signal Forms are experimental. The API may change in future releases. Avoid using experimental APIs in production applications without understanding the risks.

Signal Forms manage form state using Angular signals to provide automatic synchronization between your data model and the UI with Angular Signals.

This guide walks you through the core concepts to create forms with Signal Forms. Here's how it works:

Creating your first form

1. Create a form model with signal()

Every form starts by creating a signal that holds your form's data model:

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

2. Pass the form model to form() to create a FieldTree

Then, you pass your form model into the form() function to create a field tree - an object structure that mirrors your model's shape, allowing you to access fields with dot notation:

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

3. Bind HTML inputs with [field] directive

Next, you bind your HTML inputs to the form using the [field] directive, which creates two-way binding between them:

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

As a result, user changes (such as typing in the field) automatically updates the form.

NOTE: The [field] directive also syncs field state for attributes like required, disabled, and readonly when appropriate.

4. Read field values with value()

You can access field state by calling the field as a function. This returns a FieldState object containing reactive signals for the field's value, validation status, and interaction state:

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

To read the field's current value, access the value() signal:

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

5. Update field values with set()

You can programmatically update a field's value using the value.set() method. This updates both the field and the underlying model signal:

// Update the value programmaticallyloginForm.email().value.set('alice@wonderland.com');

As a result, both the field value and the model signal are updated automatically:

// The model signal is also updatedconsole.log(loginModel().email); // 'alice@wonderland.com'

Here's a complete example:

Basic usage

The [field] directive works with all standard HTML input types. Here are the most common patterns:

Text inputs

Text inputs work with various type attributes and textareas:

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

Numbers

Number inputs automatically convert between strings and numbers:

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

Date and time

Date inputs store values as YYYY-MM-DD strings, and time inputs use HH:mm format:

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

If you need to convert date strings to Date objects, you can do so by passing the field value into Date():

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

Multiline text

Textareas work the same way as text inputs:

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

Checkboxes

Checkboxes bind to boolean values:

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

Multiple checkboxes

For multiple options, create a separate boolean field for each:

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

Radio buttons

Radio buttons work similarly to checkboxes. As long as the radio buttons use the same [field] value, Signal Forms will automatically bind the same name attribute to all of them:

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

When a user selects a radio button, the form field stores the value from that radio button's value attribute. For example, selecting "Premium" sets form.plan().value() to "premium".

Select dropdowns

Select elements work with both static and dynamic options:

<!-- 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>

NOTE: Multiple select (<select multiple>) is not supported by the [field] directive at this time.

Validation and state

Signal Forms provides built-in validators that you can apply to your form fields. To add validation, pass a schema function as the second argument to form():

const loginForm = form(loginModel, (schemaPath) => {  debounce(schemaPath.email, 500);  required(schemaPath.email);  email(schemaPath.email);});

The schema function receives a schema path parameter that provides paths to your fields for configuring validation rules.

Common validators include:

You can also customize error messages by passing an options object as the second argument to the validator:

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

Each form field exposes its validation state through signals. For example, you can check field().valid() to see if validation passes, field().touched() to see if the user has interacted with it, and field().errors() to get the list of validation errors.

Here's a complete example:

Field State Signals

Every field() provides these state signals:

State Description
valid() Returns true if the field passes all validation rules
touched() Returns true if the user has focused and blurred the field
dirty() Returns true if the user has changed the value
disabled() Returns true if the field is disabled
readonly() Returns true if the field is readonly
pending() Returns true if async validation is in progress
errors() Returns an array of validation errors with kind and message properties

Next steps

To learn more about Signal Forms and how it works, check out the in-depth guides:

  • Overview - Introduction to Signal Forms and when to use them
  • Form models - Creating and managing form data with signals
  • Field state management - Working with validation state, interaction tracking, and field visibility
  • Validation - Built-in validators, custom validation rules, and async validation