@signaltree/ng-forms 4.1.2 → 4.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -24
- package/package.json +3 -3
- package/LICENSE +0 -54
package/README.md
CHANGED
|
@@ -1,16 +1,89 @@
|
|
|
1
1
|
# @signaltree/ng-forms
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Tree-structured signal forms for Angular 21+**. When Angular's native signal forms aren't enough—add persistence, wizards, history tracking, and nested state management.
|
|
4
4
|
|
|
5
5
|
**Bundle size: 3.38KB gzipped**
|
|
6
6
|
|
|
7
|
+
## Why ng-forms?
|
|
8
|
+
|
|
9
|
+
Angular 21 introduced native signal forms with `FormField<T>`, which work great for simple, flat forms. **ng-forms is for complex forms** that need:
|
|
10
|
+
|
|
11
|
+
### **🌲 Tree-Structured State**
|
|
12
|
+
```typescript
|
|
13
|
+
// Angular 21: Flat fields, no relationships
|
|
14
|
+
const name = formField('');
|
|
15
|
+
const email = formField('');
|
|
16
|
+
|
|
17
|
+
// ng-forms: Hierarchical structure that mirrors your data model
|
|
18
|
+
const form = createFormTree({
|
|
19
|
+
user: { name: '', email: '' },
|
|
20
|
+
address: { street: '', city: '', zip: '' }
|
|
21
|
+
});
|
|
22
|
+
// Access nested: form.$.user.name()
|
|
23
|
+
// Validate paths: 'address.zip'
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### **💾 Auto-Persistence**
|
|
27
|
+
```typescript
|
|
28
|
+
// Angular 21: No persistence, build it yourself
|
|
29
|
+
// ng-forms: Built-in with debouncing
|
|
30
|
+
const form = createFormTree(initialState, {
|
|
31
|
+
persistKey: 'checkout-draft',
|
|
32
|
+
storage: localStorage,
|
|
33
|
+
persistDebounceMs: 500
|
|
34
|
+
});
|
|
35
|
+
// Auto-saves changes, restores on init
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### **🧙 Wizard & Multi-Step Forms**
|
|
39
|
+
```typescript
|
|
40
|
+
// Angular 21: Build from scratch
|
|
41
|
+
// ng-forms: First-class wizard support
|
|
42
|
+
const wizard = createWizardForm([
|
|
43
|
+
{ fields: ['profile.name', 'profile.email'] },
|
|
44
|
+
{ fields: ['address.street', 'address.city'] }
|
|
45
|
+
], initialValues);
|
|
46
|
+
wizard.nextStep(); // Automatic field visibility management
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### **↩️ History / Undo-Redo**
|
|
50
|
+
```typescript
|
|
51
|
+
// Angular 21: Not available
|
|
52
|
+
// ng-forms: Built-in
|
|
53
|
+
const form = withFormHistory(createFormTree(initialState));
|
|
54
|
+
form.undo();
|
|
55
|
+
form.redo();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### **🔗 Reactive Forms Bridge**
|
|
59
|
+
```typescript
|
|
60
|
+
// Angular 21: New API, migration required
|
|
61
|
+
// ng-forms: Works with existing FormGroup/FormControl
|
|
62
|
+
<form [formGroup]="profile.form" (ngSubmit)="save()">
|
|
63
|
+
<input formControlName="name" />
|
|
64
|
+
</form>
|
|
65
|
+
// Signals AND reactive forms, incremental migration
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### **⚙️ Declarative Configuration**
|
|
69
|
+
```typescript
|
|
70
|
+
// Angular 21: Per-field imperative setup
|
|
71
|
+
// ng-forms: Centralized, glob-pattern configs
|
|
72
|
+
fieldConfigs: {
|
|
73
|
+
'email': { validators: [validators.email()], debounceMs: 300 },
|
|
74
|
+
'payment.card.*': { validators: validators.required() }
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Use Angular 21 signal forms for simple forms. Use ng-forms for enterprise apps with complex state, persistence, and workflow requirements.**
|
|
79
|
+
|
|
7
80
|
## Installation
|
|
8
81
|
|
|
9
82
|
```bash
|
|
10
83
|
pnpm add @signaltree/core @signaltree/ng-forms
|
|
11
84
|
```
|
|
12
85
|
|
|
13
|
-
>
|
|
86
|
+
> **Compatibility**: Angular 17+ with TypeScript 5.5+. Angular 21+ recommended for best experience. Works alongside Angular's native signal forms—use both where appropriate.
|
|
14
87
|
|
|
15
88
|
## Quick start
|
|
16
89
|
|
|
@@ -97,30 +170,58 @@ The returned `FormTree` exposes:
|
|
|
97
170
|
- **Signal ↔ Observable bridge**: Convert signals to RxJS streams for interoperability
|
|
98
171
|
- **Template-driven adapter**: `SignalValueDirective` bridges standalone signals with `ngModel`
|
|
99
172
|
|
|
100
|
-
##
|
|
173
|
+
## Angular 21 Interoperability
|
|
101
174
|
|
|
102
|
-
|
|
175
|
+
**ng-forms complements Angular 21's native signal forms**—use both in the same app:
|
|
103
176
|
|
|
104
|
-
|
|
105
|
-
-
|
|
177
|
+
### Use Angular 21 `FormField<T>` for:
|
|
178
|
+
- ✅ Simple, flat forms (login, search)
|
|
179
|
+
- ✅ Single-field validation
|
|
180
|
+
- ✅ Maximum type safety
|
|
106
181
|
|
|
107
|
-
|
|
108
|
-
|
|
182
|
+
### Use ng-forms `createFormTree()` for:
|
|
183
|
+
- ✅ Nested object structures (user + address + payment)
|
|
184
|
+
- ✅ Forms with persistence/auto-save
|
|
185
|
+
- ✅ Wizard/multi-step flows
|
|
186
|
+
- ✅ History/undo requirements
|
|
187
|
+
- ✅ Complex conditional logic
|
|
188
|
+
- ✅ Migration from reactive forms
|
|
109
189
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
190
|
+
### Hybrid Example: Simple Fields + Complex Tree
|
|
191
|
+
```typescript
|
|
192
|
+
import { formField } from '@angular/forms';
|
|
193
|
+
import { createFormTree } from '@signaltree/ng-forms';
|
|
194
|
+
|
|
195
|
+
@Component({...})
|
|
196
|
+
class CheckoutComponent {
|
|
197
|
+
// Simple field: Use Angular 21 native
|
|
198
|
+
promoCode = formField('');
|
|
199
|
+
|
|
200
|
+
// Complex nested state: Use ng-forms
|
|
201
|
+
checkout = createFormTree({
|
|
202
|
+
shipping: { name: '', address: '', city: '', zip: '' },
|
|
203
|
+
payment: { card: '', cvv: '', expiry: '' },
|
|
204
|
+
items: [] as CartItem[]
|
|
205
|
+
}, {
|
|
206
|
+
persistKey: 'checkout-draft',
|
|
207
|
+
fieldConfigs: {
|
|
208
|
+
'shipping.zip': { validators: validators.zipCode() },
|
|
209
|
+
'payment.card': { validators: validators.creditCard(), debounceMs: 300 }
|
|
210
|
+
}
|
|
211
|
+
});
|
|
117
212
|
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
userGroupControl.connect(userSignal);
|
|
213
|
+
// Both work together seamlessly
|
|
214
|
+
}
|
|
121
215
|
```
|
|
122
216
|
|
|
123
|
-
|
|
217
|
+
### Connecting to Reactive Forms
|
|
218
|
+
```ts
|
|
219
|
+
import { toWritableSignal } from '@signaltree/core';
|
|
220
|
+
|
|
221
|
+
// Convert ng-forms signals to work with Angular's .connect()
|
|
222
|
+
const nameSignal = toWritableSignal(formTree.$.user.name);
|
|
223
|
+
reactiveControl.connect(nameSignal);
|
|
224
|
+
```
|
|
124
225
|
|
|
125
226
|
## Form tree configuration
|
|
126
227
|
|
|
@@ -222,16 +323,27 @@ History tracking works at the FormGroup level so it plays nicely with external u
|
|
|
222
323
|
|
|
223
324
|
Use `SignalValueDirective` to keep standalone signals and `ngModel` fields aligned in legacy sections while new pages migrate to forms-first APIs.
|
|
224
325
|
|
|
225
|
-
## When to
|
|
326
|
+
## When to use ng-forms vs Angular 21 signal forms
|
|
327
|
+
|
|
328
|
+
| Scenario | Recommendation |
|
|
329
|
+
|----------|---------------|
|
|
330
|
+
| Login form (2-3 fields) | ✅ Angular 21 `FormField` |
|
|
331
|
+
| Search bar with filters | ✅ Angular 21 `FormField` |
|
|
332
|
+
| User profile with nested address | ✅ **ng-forms** (tree structure) |
|
|
333
|
+
| Checkout flow (shipping + payment + items) | ✅ **ng-forms** (persistence + wizard) |
|
|
334
|
+
| Multi-step onboarding (5+ steps) | ✅ **ng-forms** (wizard API) |
|
|
335
|
+
| Form with auto-save drafts | ✅ **ng-forms** (built-in persistence) |
|
|
336
|
+
| Complex editor with undo/redo | ✅ **ng-forms** (history tracking) |
|
|
337
|
+
| Migrating from reactive forms | ✅ **ng-forms** (FormGroup bridge) |
|
|
338
|
+
| Dynamic form with conditional fields | ✅ **ng-forms** (conditionals config) |
|
|
339
|
+
| Form synced with global app state | ✅ **ng-forms** (SignalTree integration) |
|
|
226
340
|
|
|
227
|
-
|
|
228
|
-
- Workflows that require persistence, auto-save, or offline drafts
|
|
229
|
-
- Multi-step wizards or surveys with dynamic branching
|
|
230
|
-
- Applications that benefit from first-class signal APIs around Angular forms
|
|
341
|
+
**Rule of thumb**: If your form data is a nested object or needs workflow features (persistence/wizards/history), use ng-forms. For simple flat forms, Angular 21's native signal forms are perfect.
|
|
231
342
|
|
|
232
343
|
## Links
|
|
233
344
|
|
|
234
345
|
- [SignalTree Documentation](https://signaltree.io)
|
|
346
|
+
- [Angular 21 Migration Guide](./ANGULAR21-MIGRATION.md)
|
|
235
347
|
- [Core Package](https://www.npmjs.com/package/@signaltree/core)
|
|
236
348
|
- [GitHub Repository](https://github.com/JBorgia/signaltree)
|
|
237
349
|
- [Demo Application](https://signaltree.io/examples)
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signaltree/ng-forms",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.4",
|
|
4
4
|
"description": "Complete Angular forms integration for SignalTree. FormTree creation, custom directives, validators, form state tracking, and RxJS bridge.",
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"@angular/core": "^20.3.0",
|
|
7
7
|
"@angular/forms": "^20.3.0",
|
|
8
|
-
"@signaltree/core": "4.1.
|
|
8
|
+
"@signaltree/core": "4.1.4",
|
|
9
9
|
"rxjs": "^7.0.0"
|
|
10
10
|
},
|
|
11
11
|
"sideEffects": false,
|
|
@@ -43,4 +43,4 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"tslib": "^2.3.0"
|
|
45
45
|
}
|
|
46
|
-
}
|
|
46
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
BUSINESS SOURCE LICENSE 1.1
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Jonathan D Borgia
|
|
4
|
-
|
|
5
|
-
This Business Source License 1.1 ("License") governs the use of the software and associated documentation files (the "Software"). You are granted a limited license to use the Software under the terms of this License.
|
|
6
|
-
|
|
7
|
-
1. Definitions
|
|
8
|
-
|
|
9
|
-
"Change Date" means the date on which the Change License set out in section 6 will apply to the Software. The Change Date for this release is 2028-09-05.
|
|
10
|
-
|
|
11
|
-
"Change License" means the open source license that will apply to the Software on and after the Change Date. The Change License for this release is the MIT License.
|
|
12
|
-
|
|
13
|
-
"Licensor" means the copyright owner granting rights under this License (Jonathan D Borgia).
|
|
14
|
-
|
|
15
|
-
"You" ("Licensee") means an individual or legal entity exercising rights under this License who has not violated the terms of this License or had their rights terminated.
|
|
16
|
-
|
|
17
|
-
2. License Grant
|
|
18
|
-
|
|
19
|
-
Subject to the terms and conditions of this License, Licensor hereby grants You a non-exclusive, non-transferable, worldwide license to use, reproduce, display, perform, and distribute the Software, and to make modifications and derivative works for internal use, until the Change Date.
|
|
20
|
-
|
|
21
|
-
3. Commercial Use
|
|
22
|
-
|
|
23
|
-
You may use the Software in commercial applications, including for providing services, selling products that include the Software, or otherwise exploiting the Software commercially, subject to the other terms of this License.
|
|
24
|
-
|
|
25
|
-
4. Limitations and Conditions
|
|
26
|
-
|
|
27
|
-
a. You may not remove or alter this License, the copyright notice, or notices of the Change Date.
|
|
28
|
-
|
|
29
|
-
b. You may not publicly offer a modified version of the Software that would directly compete with Licensor's public offering of the Software if doing so would circumvent the intent of this License.
|
|
30
|
-
|
|
31
|
-
c. Except as expressly provided in this License, no rights are granted to You under any patent or trademark of Licensor.
|
|
32
|
-
|
|
33
|
-
5. Disclaimer and Limitation of Liability
|
|
34
|
-
|
|
35
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. TO THE FULLEST EXTENT PERMITTED BY LAW, LICENSOR WILL NOT BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM OR RELATING TO THE SOFTWARE.
|
|
36
|
-
|
|
37
|
-
6. Change License
|
|
38
|
-
|
|
39
|
-
On and after the Change Date specified above, the Software will be licensed under the Change License (MIT License) on the same terms and conditions as set forth by that Change License.
|
|
40
|
-
|
|
41
|
-
7. Governing Law
|
|
42
|
-
|
|
43
|
-
This License will be governed by and construed in accordance with the laws of the State of New York, USA, without regard to conflict of law principles.
|
|
44
|
-
|
|
45
|
-
8. Accepting this License
|
|
46
|
-
|
|
47
|
-
You accept this License by copying, modifying, or distributing the Software or any portion thereof.
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
LICENSE NOTE
|
|
52
|
-
|
|
53
|
-
- Original license file replaced on 2025-09-05 to Business Source License 1.1. Change Date: 2028-09-05. Change License: MIT.
|
|
54
|
-
or standard modifications for your own applications.
|