Getting Started with Signal Forms in Angular v21 — Part 1
H ey Devs,

Angular signal form
Getting Started with Signal Forms in Angular v21 — Part 1
H ey Devs,
The long wait is over! With the upcoming Angular v21, we can now experiment with a new way to handle forms: Signal Forms. This feature is now available for developers to explore ( not ready for production ), so let’s dive in.
👉 Not a Medium member? You can read the full article for free by clicking here 🔗
What are Signal Forms and How Do They Differ from Reactive Forms?
Signal Forms are a new approach to creating forms in Angular that are powered by signals. They are conceptually similar to reactive forms, but with a key difference: they use signals instead of observables to manage state.
One of the most immediate changes you’ll notice is in the template. Instead of the formControlName directive, we now use a new control directive.
What is the purpose of the control directive?
The control directive is the bridge that connects a form field in your model to a UI element (like an <input>) in your template. Its main responsibilities are
- Two-way data binding: It binds the field’s value to the UI control’s value and vice versa.
- State synchronization: It binds additional form-related states, such as
disabled,required, etc., from the field to the UI control. - Event relaying: It relays relevant UI events back to the field. For example, it marks a field as
touchedon ablurevent.
How to Implement a Signal Form
Let’s walk through an implementation using a simple registration form.
First, we need to create a signal that represents our form’s data model.
import { signal } from '@angular/core';
// ...
readonly register = signal({
firstName: "",
lastName: "",
gender: "M",
dob: "",
terms: false,
});
Next, we create the form instance using the form function provided by @angular/forms/signals.
import { form } from '@angular/forms/signals';
// ...
readonly registerForm = form(this.register);
What does the form function do?
The form function uses the signal you provide as the single source of truth; it does not maintain its own internal copy of the data. This means that when you update a value on a form field, the original signal model is updated directly.
Note: To keep the code clean, I created a separate readonly signal for the form model. However, you can also define the signal directly inside the form function if you prefer:
readonly registerForm = form(signal({
firstName: "",
lastName: "",
gender: "M",
dob: "",
terms: false,
}));
Now, we can use the control directive in our template to bind the form fields to their UI controls.
<label>First Name</label>
<input [control]="registerForm.controls.firstName" type="text" />
As you can see, the process is similar to reactive forms, but we use the [control] directive to establish the binding.
How to Get the Form’s Value
You can get the current value of the form by simply calling the form signal.
console.log(this.registerForm.value());
Implementing Validation
To add field logic, such as validation, you provide a callback function as the second argument to the form method. This function receives a path object that you use to specify which field the validators apply to.
import { form, required, minLength } from '@angular/forms/signals';
// ...
readonly registerForm = form(this.register, (path) => [
required(path.controls.firstName),
minLength(path.controls.firstName, 3)
]);
A neat feature is that when you add validators, the corresponding HTML attributes (e.g., required, minlength, maxlength) are automatically added to the bound HTML element in the browser.

Html attributes — angular signal forms
Displaying Error Messages
Validation errors are available at both the form level and on each individual control. To access the errors for a specific control, you can call its errors() method.
@if (registerForm.controls.firstName.errors(); as errors) {
@for (error of errors; track error) {
<small class="error-message">{{ error.message }}</small>
}
}
The errors() method returns an array of ValidationError objects. Each error object in this array contains the following properties:
{
/** Identifies the kind of error (e.g., 'required', 'minLength'). */
readonly kind: string;
/** The field associated with this error. */
readonly field: Field<unknown>;
/** A human-readable error message. */
readonly message?: string;
}
You might be wondering if it’s possible to configure a custom error message for each validator. The answer is yes! You can define a custom message directly within the validator configuration.
readonly registerForm = form(this.register, (path) => [
required(path.controls.firstName, { message: "First Name is required." }),
minLength(path.controls.firstName, 3, { message: "First Name must be at least 3 characters." })
]);
Final Code :
import { CommonModule } from "@angular/common";
import { Component, effect, signal } from "@angular/core";
import { Control, form, minLength, required } from "@angular/forms/signals";
@Component({
selector: "app-signal-form",
standalone: true,
imports: [Control , CommonModule],
template: `
<div class="form-header">
<h1> Registration Form using signals form </h1>
</div>
<form autocomplete="off" (submit)="submit()" >
<div class="form-container">
<div class="form-field">
<label>First Name</label>
<input [control]="registerForm.firstName" type="text" />
@if(registerForm.firstName().errors()) {
@for(error of registerForm.firstName().errors(); track error) {
<!-- @if(error.kind === 'required'){
<small class="error-message"> First Name is required </small>
} -->
<small class="error-message">{{ error.message }}</small>
}
}
</div>
<div class="form-field">
<label>Last Name</label>
<input [control]="registerForm.lastName" type="text" />
</div>
<div class="form-field">
<label>Gender</label>
<select [control]="registerForm.gender">
<option value="M">Male</option>
<option value="F">Female</option>
<option value="O">Other</option>
</select>
</div>
<div class="form-field">
<label>Date of Birth</label>
<input [control]="registerForm.dob" type="date" />
</div>
<div class="form-field">
<label>
<input [control]="registerForm.terms" type="checkbox" />
Accept Terms
</label>
</div>
</div>
<div class="button-container">
<button type="button" (click)="clear()">Clear</button>
<button [disabled]="!registerForm().valid()" type="submit">Register</button>
</div>
</form>
{{
registerForm().value() | json
}}
`,
styles: `
.form-header {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 16px;
}
.form-container {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
gap: 16px;
}
.form-field {
display: flex;
flex-direction: column;
gap: 5px;
width: 450px;
}
form {
display: flex;
flex-direction: column;
flex-wrap: wrap;
gap: 10px;
}
label {
font-size: 14px;
font-weight: 600;
}
input, select {
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
input.invalid {
border: 1.5px solid #e74c3c;
background: #fff6f6;
}
.error-message {
color: #e74c3c;
font-size: 13px;
margin-top: 2px;
}
button {
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
background-color: #007bff;
color: white;
width: 100px;
}
button:disabled {
background-color: #ccc;
color: #000;
}
.button-container {
display: flex;
justify-content: center;
align-items: center;
margin-top: 16px;
gap: 16px;
}
`,
})
export class SignalForm {
readonly register = signal({
firstName: "",
lastName: "",
gender: "M",
dob: "",
terms: false,
});
readonly registerForm = form(this.register, (path) => {
required(path.firstName, { message: "First Name is required" }),
minLength(path.firstName, 3 , { message : "First Name must be at least 3 characters long" })
});
submit() {
console.log(this.registerForm().value());
}
clear() {}
constructor(){
effect(() => {
console.log(this.registerForm.firstName().value());
});
effect(() => {
console.log(this.registerForm.firstName().valid());
});
}
}

Angular signal form
What’s Next?
In this post, we’ve covered the basic implementation of Signal Forms and how to add validation. In a future post, we will explore more advanced topics like implementing schemas, handling form submission, and updating form values programmatically. Stay tuned!
Thanks for reading! If this was helpful, consider clapping 👏 and following for more Angular tips. Got questions or suggestions? Drop them in the comments below!
✍️ Author: **Vetriselvan Panneerselvam**
👨💻 Frontend Developer | 💡 Code Enthusiast | 📚 Lifelong Learner | ✍️ Tech Blogger | 🌍 Freelance Developer
메타데이터
- post_id
- 13212078820d
- slug
- getting-started-with-signal-forms-in-angular-v21-part-1-13212078820d
- url
- https://medium.com/@vetriselvan_11/getting-started-with-signal-forms-in-angular-v21-part-1-13212078820d
- canonical_url
- https://medium.com/@vetriselvan_11/getting-started-with-signal-forms-in-angular-v21-part-1-13212078820d
- author_url
- https://medium.com/@vetriselvan_11
- status
- ok
- fetched_at
- 2026-06-10 13:10:15