|
| 1 | +# Comparison with other form approaches |
| 2 | + |
| 3 | +Angular provides three approaches to building forms: Signal Forms, Reactive Forms, and Template-driven Forms. Each has distinct patterns for managing state, validation, and data flow. This guide helps you understand the differences and choose the right approach for your project. |
| 4 | + |
| 5 | +NOTE: Signal Forms are [experimental](reference/releases#experimental) as of Angular v21. The API may change before stabilizing. |
| 6 | + |
| 7 | +## Quick comparison |
| 8 | + |
| 9 | +| Feature | Signal Forms | Reactive Forms | Template-driven Forms | |
| 10 | +| ---------------- | ---------------------------------- | ------------------------------------- | ----------------------- | |
| 11 | +| Source of truth | User-defined writable signal model | `FormControl`/`FormGroup` | User model in component | |
| 12 | +| Type safety | Inferred from model | Explicit with typed forms | Minimal | |
| 13 | +| Validation | Schema with path-based validators | List of validators passed to Controls | Directive-based | |
| 14 | +| State management | Signal-based | Observable-based | Angular-managed | |
| 15 | +| Setup | Signal + schema function | FormControl tree | NgModel in template | |
| 16 | +| Best for | Signal-based apps | Complex forms | Simple forms | |
| 17 | +| Learning curve | Medium | Medium-High | Low | |
| 18 | +| Status | Experimental (v21+) | Stable | Stable | |
| 19 | + |
| 20 | +## By example: Login form |
| 21 | + |
| 22 | +The best way to understand the differences is to see the same form implemented in all three approaches. |
| 23 | + |
| 24 | +<docs-code-multifile> |
| 25 | + <docs-code header="Signal forms" path="adev/src/content/examples/signal-forms/src/comparison/app/signal-forms.ts"/> |
| 26 | + <docs-code header="Reactive forms" path="adev/src/content/examples/signal-forms/src/comparison/app/reactive-forms.ts"/> |
| 27 | + <docs-code header="Template-driven forms" path="adev/src/content/examples/signal-forms/src/comparison/app/template-driven-forms.ts"/> |
| 28 | +</docs-code-multifile> |
| 29 | + |
| 30 | +## Understanding the differences |
| 31 | + |
| 32 | +The three approaches make different design choices that affect how you write and maintain your forms. These differences stem from where each approach stores form state and how it manages validation. |
| 33 | + |
| 34 | +### Where your form data lives |
| 35 | + |
| 36 | +The most fundamental difference is where each approach considers the "source of truth" for form values. |
| 37 | + |
| 38 | +Signal Forms stores data in a writable signal. When you need the current form values, you call the signal: |
| 39 | + |
| 40 | +```ts |
| 41 | +const credentials = this.loginModel(); // { email: '...', password: '...' } |
| 42 | +``` |
| 43 | + |
| 44 | +This keeps your form data in a single reactive container that automatically notifies Angular when values change. The form structure mirrors your data model exactly. |
| 45 | + |
| 46 | +Reactive Forms stores data inside FormControl and FormGroup instances. You access values through the form hierarchy: |
| 47 | + |
| 48 | +```ts |
| 49 | +const credentials = this.loginForm.value; // { email: '...', password: '...' } |
| 50 | +``` |
| 51 | + |
| 52 | +This separates form state management from your component's data model. The form structure is explicit but requires more setup code. |
| 53 | + |
| 54 | +Template-driven Forms stores data in component properties. You access values directly: |
| 55 | + |
| 56 | +```ts |
| 57 | +const credentials = { email: this.email, password: this.password }; |
| 58 | +``` |
| 59 | + |
| 60 | +This is the most direct approach but requires manually assembling values when you need them. Angular manages form state through directives in the template. |
| 61 | + |
| 62 | +### How validation works |
| 63 | + |
| 64 | +Each approach defines validation rules differently, affecting where your validation logic lives and how you maintain it. |
| 65 | + |
| 66 | +Signal Forms uses a schema function where you bind validators to field paths: |
| 67 | + |
| 68 | +```ts |
| 69 | +loginForm = form(this.loginModel, (fieldPath) => { |
| 70 | + required(fieldPath.email, { message: 'Email is required' }); |
| 71 | + email(fieldPath.email, { message: 'Enter a valid email address' }); |
| 72 | +}); |
| 73 | +``` |
| 74 | + |
| 75 | +All validation rules live together in one place. The schema function runs once during form creation, and validators execute automatically when field values change. Error messages are part of the validation definition. |
| 76 | + |
| 77 | +Reactive Forms attaches validators when creating controls: |
| 78 | + |
| 79 | +```ts |
| 80 | +loginForm = new FormGroup({ |
| 81 | + email: new FormControl('', [Validators.required, Validators.email]) |
| 82 | +}); |
| 83 | +``` |
| 84 | + |
| 85 | +Validators are tied to individual controls in the form structure. This distributes validation across your form definition. Error messages typically live in your template. |
| 86 | + |
| 87 | +Template-driven Forms uses directive attributes in the template: |
| 88 | + |
| 89 | +```html |
| 90 | +<input [(ngModel)]="email" required email /> |
| 91 | +``` |
| 92 | + |
| 93 | +Validation rules live in your template alongside the HTML. This keeps validation close to the UI but spreads logic across template and component. |
| 94 | + |
| 95 | +### Type safety and autocomplete |
| 96 | + |
| 97 | +TypeScript integration differs significantly between approaches, affecting how much the compiler helps you avoid errors. |
| 98 | + |
| 99 | +Signal Forms infers types from your model structure: |
| 100 | + |
| 101 | +```ts |
| 102 | +const loginModel = signal({ email: '', password: '' }); |
| 103 | +const loginForm = form(loginModel); |
| 104 | +// TypeScript knows: loginForm.email exists and returns FieldState<string> |
| 105 | +``` |
| 106 | + |
| 107 | +You define your data shape once in the signal, and TypeScript automatically knows what fields exist and their types. Accessing `loginForm.username` (which doesn't exist) produces a type error. |
| 108 | + |
| 109 | +Reactive Forms requires explicit type annotations with typed forms: |
| 110 | + |
| 111 | +```ts |
| 112 | +const loginForm = new FormGroup({ |
| 113 | + email: new FormControl<string>(''), |
| 114 | + password: new FormControl<string>('') |
| 115 | +}); |
| 116 | +// TypeScript knows: loginForm.controls.email is FormControl<string> |
| 117 | +``` |
| 118 | + |
| 119 | +You specify types for each control individually. TypeScript validates your form structure, but you maintain type information separately from your data model. |
| 120 | + |
| 121 | +Template-driven Forms offers minimal type safety: |
| 122 | + |
| 123 | +```ts |
| 124 | +email = ''; |
| 125 | +password = ''; |
| 126 | +// TypeScript only knows these are strings, no form-level typing |
| 127 | +``` |
| 128 | + |
| 129 | +TypeScript understands your component properties but has no knowledge of form structure or validation. You lose compile-time checking for form operations. |
| 130 | + |
| 131 | +## Choose your approach |
| 132 | + |
| 133 | +### Use Signal Forms if: |
| 134 | + |
| 135 | +- You're building new signal-based applications (Angular v21+) |
| 136 | +- You want type safety inferred from your model structure |
| 137 | +- You're comfortable working with experimental features |
| 138 | +- Schema-based validation appeals to you |
| 139 | +- Your team is familiar with signals |
| 140 | + |
| 141 | +### Use Reactive Forms if: |
| 142 | + |
| 143 | +- You need production-ready stability |
| 144 | +- You're building complex, dynamic forms |
| 145 | +- You prefer observable-based patterns |
| 146 | +- You need fine-grained control over form state |
| 147 | +- You're working on an existing reactive forms codebase |
| 148 | + |
| 149 | +### Use Template-driven Forms if: |
| 150 | + |
| 151 | +- You're building simple forms (login, contact, search) |
| 152 | +- You're doing rapid prototyping |
| 153 | +- Your form logic is straightforward |
| 154 | +- You prefer keeping form logic in templates |
| 155 | +- You're working on an existing template-driven codebase |
| 156 | + |
| 157 | +## Next steps |
| 158 | + |
| 159 | +To learn more about each approach: |
| 160 | + |
| 161 | +- **Signal Forms**: See the [Overview guide](guide/forms/signal-forms/overview) to get started, or dive into [Form Models](guide/forms/signal-forms/models), [Validation](guide/forms/signal-forms/validation), and [Field State Management](guide/forms/signal-forms/field-state-management) |
| 162 | +- **Reactive Forms**: See the [Reactive Forms guide](guide/forms/reactive-forms) in Angular documentation |
| 163 | +- **Template-driven Forms**: See the [Template-driven Forms guide](guide/forms/template-driven-forms) in Angular documentation |
0 commit comments