mirror of https://github.com/abpframework/abp.git
Browse Source
Introduces a new community article detailing the experimental Signal-Based Forms API in Angular 21. The article covers motivations, type safety, two-way binding, validation, and practical usage examples, highlighting the advantages over traditional Reactive Forms. Includes a cover image for the article.pull/24198/head
2 changed files with 322 additions and 0 deletions
|
After Width: | Height: | Size: 1.6 MiB |
@ -0,0 +1,322 @@ |
|||
# Signal-Based Forms in Angular 21: Why You’ll Never Miss Reactive Forms Again |
|||
|
|||
Angular 21 introduces one of the most exciting developments in the modern edition of Angular: **Signal-Based Forms**. Built directly on the reactive foundation of Angular signals, this new experimental API provides a cleaner, more intuitive, strongly typed, and ergonomic approach for managing form state—without the heavy boilerplate of Reactive Forms. |
|||
|
|||
> ⚠️ **Important:** Signal Forms are *experimental*. |
|||
> Their API can change. Avoid using them in critical production scenarios unless you understand the risks. |
|||
|
|||
Despite this, Signal Forms clearly represent Angular’s future direction. |
|||
--- |
|||
|
|||
## Why Signal Forms? |
|||
|
|||
Traditionally in Angular, building forms has involved several concerns: |
|||
|
|||
- Tracking values |
|||
- Managing UI interaction states (touched, dirty) |
|||
- Handling validation |
|||
- Keeping UI and model in sync |
|||
|
|||
Reactive Forms solved many challenges but introduced their own: |
|||
|
|||
- Verbosity FormBuilder API |
|||
- Required subscriptions (valueChanges) |
|||
- Manual cleaning |
|||
- Difficult nested forms |
|||
- Weak type-safety |
|||
|
|||
**Signal Forms solve these problems through:** |
|||
|
|||
1." Automatic synchronization |
|||
2." Full type safety |
|||
3." Schema-based validation |
|||
4." Fine-grained reactivity |
|||
5." Drastically reduced boilerplate |
|||
6." Natural integration with Angular Signals |
|||
|
|||
--- |
|||
|
|||
### 1. Form Models — The Core of Signal Forms |
|||
|
|||
A **form model** is simply a writable signal holding the structure of your form data. |
|||
|
|||
```ts |
|||
import { Component, signal } from '@angular/core'; |
|||
import { form, Field } from '@angular/forms/signals'; |
|||
|
|||
@Component({ |
|||
selector: 'app-login', |
|||
imports: [Field], |
|||
template: ` |
|||
<input type="email" [field]="loginForm.email" /> |
|||
<input type="password" [field]="loginForm.password" /> |
|||
`, |
|||
}) |
|||
export class LoginComponent { |
|||
loginModel = signal({ |
|||
email: '', |
|||
password: '', |
|||
}); |
|||
|
|||
loginForm = form(this.loginModel); |
|||
} |
|||
``` |
|||
|
|||
Calling `form(model)` creates a **Field Tree** that maps directly to your model. |
|||
|
|||
--- |
|||
|
|||
### 2. Achieving Full Type Safety |
|||
|
|||
Although TypeScript can infer types from object literals, defining explicit interfaces provides maximum safety and better IDE support. |
|||
|
|||
```ts |
|||
interface LoginData { |
|||
email: string; |
|||
password: string; |
|||
} |
|||
|
|||
loginModel = signal<LoginData>({ |
|||
email: '', |
|||
password: '', |
|||
}); |
|||
|
|||
loginForm = form(loginModel); |
|||
``` |
|||
|
|||
Now: |
|||
|
|||
- `loginForm.email` → `FieldTree<string>` |
|||
- Accessing invalid fields like `loginForm.username` results in compile-time errors |
|||
|
|||
This level of type safety surpasses Reactive Forms. |
|||
|
|||
--- |
|||
|
|||
### 3. Reading Form Values |
|||
|
|||
#### Read from the model (entire form): |
|||
|
|||
```ts |
|||
onSubmit() { |
|||
const data = this.loginModel(); |
|||
console.log(data.email, data.password); |
|||
} |
|||
``` |
|||
|
|||
#### Read from an individual field: |
|||
|
|||
```html |
|||
<p>Current email: {{ loginForm.email().value() }}</p> |
|||
``` |
|||
|
|||
Each field exposes: |
|||
|
|||
- `value()` |
|||
- `valid()` |
|||
- `errors()` |
|||
- `dirty()` |
|||
- `touched()` |
|||
|
|||
All as signals. |
|||
|
|||
--- |
|||
|
|||
### 4. Updating Form Models Programmatically |
|||
|
|||
Signal Forms allow three update methods. |
|||
|
|||
#### 1. Replace the entire model |
|||
|
|||
```ts |
|||
this.userModel.set({ |
|||
name: 'Alice', |
|||
email: 'alice@example.com', |
|||
}); |
|||
``` |
|||
|
|||
#### 2. Patch specific fields |
|||
|
|||
```ts |
|||
this.userModel.update(prev => ({ |
|||
...prev, |
|||
email: newEmail, |
|||
})); |
|||
``` |
|||
|
|||
#### 3. Update a single field |
|||
|
|||
```ts |
|||
this.userForm.email().value.set(''); |
|||
``` |
|||
|
|||
This eliminates the need for: |
|||
|
|||
- `patchValue()` |
|||
- `setValue()` |
|||
- `formGroup.get('field')` |
|||
|
|||
--- |
|||
|
|||
### 5. Automatic Two-Way Binding With `[field]` |
|||
|
|||
The `[field]` directive enables perfect two-way data binding: |
|||
|
|||
```html |
|||
<input [field]="userForm.name" /> |
|||
``` |
|||
|
|||
#### How it works: |
|||
|
|||
- **User input → Field state → Model** |
|||
- **Model updates → Field state → Input UI** |
|||
|
|||
No subscriptions. |
|||
No event handlers. |
|||
No boilerplate. |
|||
|
|||
Reactive Forms could never achieve this cleanly. |
|||
|
|||
--- |
|||
|
|||
### 6. Nested Models and Arrays |
|||
|
|||
Models can contain nested object structures: |
|||
|
|||
```ts |
|||
userModel = signal({ |
|||
name: '', |
|||
address: { |
|||
street: '', |
|||
city: '', |
|||
}, |
|||
}); |
|||
``` |
|||
|
|||
Access fields easily: |
|||
|
|||
```html |
|||
<input [field]="userForm.address.street" /> |
|||
``` |
|||
|
|||
Arrays are also supported: |
|||
|
|||
```ts |
|||
orderModel = signal({ |
|||
items: [ |
|||
{ product: '', quantity: 1, price: 0 } |
|||
] |
|||
}); |
|||
``` |
|||
|
|||
Field state persists even when array items move, thanks to identity tracking. |
|||
|
|||
--- |
|||
|
|||
### 7. Schema-Based Validation |
|||
|
|||
Validation is clean and centralized: |
|||
|
|||
```ts |
|||
import { required, email } from '@angular/forms/signals'; |
|||
|
|||
const model = signal({ email: '' }); |
|||
|
|||
const formRef = form(model, { |
|||
email: [required(), email()], |
|||
}); |
|||
``` |
|||
|
|||
Field validation state is reactive: |
|||
|
|||
```ts |
|||
formRef.email().valid() |
|||
formRef.email().errors() |
|||
formRef.email().touched() |
|||
``` |
|||
|
|||
Validation no longer scatters across components. |
|||
|
|||
--- |
|||
|
|||
### 8. When Should You Use Signal Forms? |
|||
|
|||
#### New Angular 21+ apps |
|||
Signal-first architecture is the new standard. |
|||
|
|||
#### Teams wanting stronger type safety |
|||
Every field is exactly typed. |
|||
|
|||
#### Devs tired of Reactive Form boilerplate |
|||
Signal Forms drastically simplify code. |
|||
|
|||
#### Complex UI with computed reactive form state |
|||
Signals integrate perfectly. |
|||
|
|||
#### ❌ Avoid if: |
|||
- You need long-term stability |
|||
- You rely on mature Reactive Forms features |
|||
- Your app must avoid experimental APIs |
|||
|
|||
--- |
|||
|
|||
### 9. Reactive Forms vs Signal Forms |
|||
|
|||
| Feature | Reactive Forms | Signal Forms | |
|||
|--------|----------------|--------------| |
|||
| Boilerplate | High | Very low | |
|||
| Type-safety | Weak | Strong | |
|||
| Two-way binding | Manual | Automatic | |
|||
| Validation | Scattered | Centralized schema | |
|||
| Nested forms | Verbose | Natural | |
|||
| Subscriptions | Required | None | |
|||
| Change detection | Zone-heavy | Fine-grained | |
|||
|
|||
Signal Forms feel like the "modern Angular mode," while Reactive Forms increasingly feel legacy. |
|||
|
|||
--- |
|||
|
|||
### 10. Full Example: Login Form |
|||
|
|||
```ts |
|||
@Component({ |
|||
selector: 'app-login', |
|||
imports: [Field], |
|||
template: ` |
|||
<form (ngSubmit)="submit()"> |
|||
<input type="email" [field]="form.email" /> |
|||
<input type="password" [field]="form.password" /> |
|||
<button>Login</button> |
|||
</form> |
|||
`, |
|||
}) |
|||
export class LoginComponent { |
|||
model = signal({ email: '', password: '' }); |
|||
form = form(this.model); |
|||
|
|||
submit() { |
|||
console.log(this.model()); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
Minimal. Reactive. Completely type-safe. |
|||
|
|||
--- |
|||
|
|||
## **Conclusion** |
|||
|
|||
Signal Forms in Angular 21 represent a big step forward: |
|||
|
|||
- Cleaner API |
|||
- Stronger type safety |
|||
- Automatic two-way binding |
|||
- Centralized validation |
|||
- Fine-grained reactivity |
|||
- Dramatically better developer experience |
|||
|
|||
|
|||
Although these are experimental, they clearly show the future of Angular's form ecosystem. |
|||
Once you get into using Signal Forms, you may never want to use Reactive Forms again. |
|||
|
|||
--- |
|||
Loading…
Reference in new issue