@ng-modular-forms/core 0.1.0
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 +125 -0
- package/fesm2022/ng-modular-forms-core.mjs +254 -0
- package/fesm2022/ng-modular-forms-core.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/lib/form-control-base.d.ts +32 -0
- package/lib/form-handler-base.d.ts +11 -0
- package/lib/form-mapper-base.d.ts +13 -0
- package/lib/form-orchestrator-base.d.ts +50 -0
- package/lib/form.util.d.ts +5 -0
- package/lib/index.d.ts +4 -0
- package/package.json +25 -0
- package/public-api.d.ts +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# @ng-modular-forms/core
|
|
2
|
+
|
|
3
|
+
Core primitives for orchestrating complex Angular reactive forms.
|
|
4
|
+
|
|
5
|
+
## What This Provides
|
|
6
|
+
|
|
7
|
+
- Form orchestration
|
|
8
|
+
- Reactive logic isolation
|
|
9
|
+
- Data mapping layer
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @ng-modular-forms/core
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Key Concepts
|
|
18
|
+
|
|
19
|
+
### FormOrchestratorBase
|
|
20
|
+
|
|
21
|
+
Coordinates form structure and lifecycle.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
@Component({...})
|
|
25
|
+
export class ExampleComponent extends FormOrchestratorBase {
|
|
26
|
+
form = new FormGroup({});
|
|
27
|
+
|
|
28
|
+
ngOnInit() {
|
|
29
|
+
// Handlers are not required if there is no reactive logic or value change subscriptions.
|
|
30
|
+
const mainHandler = inject(ExampleFormHandler);
|
|
31
|
+
const sectionAHandler = inject(SectionAHandler);
|
|
32
|
+
|
|
33
|
+
const formOptions = {
|
|
34
|
+
mainHandler: mainHandler,
|
|
35
|
+
subHandlers: {
|
|
36
|
+
sectionA: sectionAHandler
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
this.initialize(this.form, formOptions);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Only required if forms are split across multiple components
|
|
44
|
+
onSubformReady(form: FormGroup, key: string) {
|
|
45
|
+
super.onSubformReady(form, key);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
### FormHandlerBase
|
|
53
|
+
|
|
54
|
+
Encapsulates reactive logic.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const CONTROL_NAMES = ["fieldA", "dependentField"] as const;
|
|
58
|
+
|
|
59
|
+
type ControlNames = (typeof CONTROL_NAMES)[number];
|
|
60
|
+
|
|
61
|
+
@Injectable()
|
|
62
|
+
export class SectionAHandler extends FormHandlerBase<ControlNames> {
|
|
63
|
+
override getReactiveLogic(): Subscription {
|
|
64
|
+
this.registerControls(this.form, [...CONTROL_NAMES]);
|
|
65
|
+
|
|
66
|
+
return this.valueChangesOf("fieldA").subscribe((value) => {
|
|
67
|
+
if (value) {
|
|
68
|
+
this.controls.dependentField.enable();
|
|
69
|
+
} else {
|
|
70
|
+
this.controls.dependentField.disable();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
### FormMapperBase
|
|
80
|
+
|
|
81
|
+
Handles transformations between API and form.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
export class ExampleMapper extends FormMapperBase<ApiModel, RequestModel> {
|
|
85
|
+
buildRequest(form: FormGroup) {
|
|
86
|
+
return {
|
|
87
|
+
fieldA: form.value.fieldA,
|
|
88
|
+
fieldB: form.value.fieldB,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
transformFromModel(model: ApiModel) {
|
|
93
|
+
return {
|
|
94
|
+
fieldA: model.fieldA,
|
|
95
|
+
fieldB: model.fieldB,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
### Responsibility Boundaries
|
|
104
|
+
|
|
105
|
+
| Layer | Responsibility |
|
|
106
|
+
| :--------------- | :------------------------------------------------------- |
|
|
107
|
+
| **Orchestrator** | Manages form composition and lifecycle coordination. |
|
|
108
|
+
| **Handler** | Encapsulates all reactive logic and stream management. |
|
|
109
|
+
| **Mapper** | Handles data transformation between API and Form states. |
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### No UI Included
|
|
114
|
+
|
|
115
|
+
This package does **not** provide UI components.
|
|
116
|
+
|
|
117
|
+
Use:
|
|
118
|
+
|
|
119
|
+
- @ng-modular-forms/control
|
|
120
|
+
- @ng-modular-forms/input
|
|
121
|
+
- @ng-modular-forms/material
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MIT
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, ChangeDetectorRef, HostBinding, Input, Optional, Self, Directive, signal } from '@angular/core';
|
|
3
|
+
import * as i1 from '@angular/forms';
|
|
4
|
+
import { FormGroup } from '@angular/forms';
|
|
5
|
+
import { Subscription } from 'rxjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Base implementation for custom form controls that integrate with Angular Reactive Forms (ControlValueAccessor)
|
|
9
|
+
*
|
|
10
|
+
* NOTE: This class is UI-layer only and should not be used in form orchestration logic.
|
|
11
|
+
*/
|
|
12
|
+
class FormControlBase {
|
|
13
|
+
ngControl;
|
|
14
|
+
elementRef;
|
|
15
|
+
cdr = inject(ChangeDetectorRef);
|
|
16
|
+
name;
|
|
17
|
+
placeholder = '';
|
|
18
|
+
required = false;
|
|
19
|
+
disabled = false;
|
|
20
|
+
readonly = false;
|
|
21
|
+
formControlName;
|
|
22
|
+
static nextId = 0;
|
|
23
|
+
id = `nmf-form-control-${FormControlBase.nextId++}`;
|
|
24
|
+
_value = null;
|
|
25
|
+
get value() {
|
|
26
|
+
return this._value;
|
|
27
|
+
}
|
|
28
|
+
set value(val) {
|
|
29
|
+
this._value = val;
|
|
30
|
+
}
|
|
31
|
+
onChange = (_value) => { };
|
|
32
|
+
onTouched = () => { };
|
|
33
|
+
constructor(ngControl, elementRef) {
|
|
34
|
+
this.ngControl = ngControl;
|
|
35
|
+
this.elementRef = elementRef;
|
|
36
|
+
if (this.ngControl != null) {
|
|
37
|
+
this.ngControl.valueAccessor = this;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
writeValue(value) {
|
|
41
|
+
this._value = value;
|
|
42
|
+
this.cdr.markForCheck();
|
|
43
|
+
}
|
|
44
|
+
registerOnChange(fn) {
|
|
45
|
+
this.onChange = fn;
|
|
46
|
+
}
|
|
47
|
+
registerOnTouched(fn) {
|
|
48
|
+
this.onTouched = fn;
|
|
49
|
+
}
|
|
50
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: FormControlBase, deps: [{ token: i1.NgControl, optional: true, self: true }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
|
|
51
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.20", type: FormControlBase, isStandalone: true, inputs: { name: "name", placeholder: "placeholder", required: "required", disabled: "disabled", readonly: "readonly", formControlName: "formControlName" }, host: { properties: { "id": "this.id" } }, ngImport: i0 });
|
|
52
|
+
}
|
|
53
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: FormControlBase, decorators: [{
|
|
54
|
+
type: Directive
|
|
55
|
+
}], ctorParameters: () => [{ type: i1.NgControl, decorators: [{
|
|
56
|
+
type: Optional
|
|
57
|
+
}, {
|
|
58
|
+
type: Self
|
|
59
|
+
}] }, { type: i0.ElementRef }], propDecorators: { name: [{
|
|
60
|
+
type: Input
|
|
61
|
+
}], placeholder: [{
|
|
62
|
+
type: Input
|
|
63
|
+
}], required: [{
|
|
64
|
+
type: Input
|
|
65
|
+
}], disabled: [{
|
|
66
|
+
type: Input
|
|
67
|
+
}], readonly: [{
|
|
68
|
+
type: Input
|
|
69
|
+
}], formControlName: [{
|
|
70
|
+
type: Input
|
|
71
|
+
}], id: [{
|
|
72
|
+
type: HostBinding
|
|
73
|
+
}] } });
|
|
74
|
+
|
|
75
|
+
function getControl(controlName, form) {
|
|
76
|
+
if (!form) {
|
|
77
|
+
throw new Error(`Missing form instance while getting the control of "${controlName}"`);
|
|
78
|
+
}
|
|
79
|
+
const control = form.get(controlName);
|
|
80
|
+
if (!control) {
|
|
81
|
+
throw new Error(`Missing control "${controlName}" in form "${form}"`);
|
|
82
|
+
}
|
|
83
|
+
return control;
|
|
84
|
+
}
|
|
85
|
+
function getControlValue(controlName, form) {
|
|
86
|
+
const control = getControl(controlName, form);
|
|
87
|
+
if (!control) {
|
|
88
|
+
throw new Error(`Missing control "${controlName}" in form "${form}"`);
|
|
89
|
+
}
|
|
90
|
+
const value = control.getRawValue();
|
|
91
|
+
if (value === '') {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
if (typeof value === 'string') {
|
|
95
|
+
const cleaned = value.replace(/,/g, '');
|
|
96
|
+
if (!Number.isNaN(Number(cleaned))) {
|
|
97
|
+
return parseCurrency(value);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
function parseCurrency(currencyString) {
|
|
103
|
+
if (currencyString == null) {
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
if (typeof currencyString === 'number') {
|
|
107
|
+
return currencyString;
|
|
108
|
+
}
|
|
109
|
+
return Number(currencyString.replace(/[^0-9.-]/g, ''));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
class FormHandlerBase {
|
|
113
|
+
registeredControls = {};
|
|
114
|
+
/**
|
|
115
|
+
* Registers form controls for later reactive access.
|
|
116
|
+
*/
|
|
117
|
+
registerControls(form, controlNames) {
|
|
118
|
+
controlNames.forEach((cn) => {
|
|
119
|
+
const control = getControl(cn.replace(/_/g, '.'), form);
|
|
120
|
+
if (!control) {
|
|
121
|
+
throw new Error(`Failed to register control with name: "${cn}" in form group: "${form}"`);
|
|
122
|
+
}
|
|
123
|
+
const key = cn;
|
|
124
|
+
this.registeredControls[key] = control;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
valueChangesOf(key) {
|
|
128
|
+
if (!this.registeredControls[key]) {
|
|
129
|
+
throw new Error(`Control with name: "${key}" not found. Ensure it is registered in registerControls(...)`);
|
|
130
|
+
}
|
|
131
|
+
return this.registeredControls[key].valueChanges;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
class FormMapperBase {
|
|
136
|
+
patchForm(form, data, _context, emitEvent = false) {
|
|
137
|
+
form.patchValue({
|
|
138
|
+
...data,
|
|
139
|
+
}, { emitEvent });
|
|
140
|
+
}
|
|
141
|
+
patchFromModel(form, model, store) {
|
|
142
|
+
const formModel = this.transformFromModel(model);
|
|
143
|
+
this.patchForm(form, formModel, store);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
class FormOrchestratorBase {
|
|
148
|
+
_form = signal(new FormGroup({}));
|
|
149
|
+
_mainHandler = signal(null);
|
|
150
|
+
_subHandlers = signal({});
|
|
151
|
+
_status = signal('idle');
|
|
152
|
+
_errorMessage = signal(null);
|
|
153
|
+
_loadedHandlers = signal(0);
|
|
154
|
+
_allHandlersLoaded = signal(false);
|
|
155
|
+
_logicSubscription = new Subscription();
|
|
156
|
+
form = this._form.asReadonly();
|
|
157
|
+
mainHandler = this._mainHandler.asReadonly();
|
|
158
|
+
subHandlers = this._subHandlers.asReadonly();
|
|
159
|
+
status = this._status.asReadonly();
|
|
160
|
+
errorMessage = this._errorMessage.asReadonly();
|
|
161
|
+
/**
|
|
162
|
+
* Initializes orchestration state.
|
|
163
|
+
* Must be called before any subform registration or handler execution.
|
|
164
|
+
*/
|
|
165
|
+
initialize(form, formOrchestratorOptions) {
|
|
166
|
+
const { mainHandler, subHandlers = {} } = formOrchestratorOptions ?? {};
|
|
167
|
+
this._form.set(form);
|
|
168
|
+
this._mainHandler.set(mainHandler ?? null);
|
|
169
|
+
this._subHandlers.set(subHandlers);
|
|
170
|
+
this._loadedHandlers.set(0);
|
|
171
|
+
const subHandlerCount = Object.keys(this.subHandlers());
|
|
172
|
+
if (subHandlerCount.length === 0) {
|
|
173
|
+
this.loadMainHandler();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
setForm(form) {
|
|
177
|
+
this._form.set(form);
|
|
178
|
+
}
|
|
179
|
+
getHandler(key) {
|
|
180
|
+
return this.subHandlers()[key];
|
|
181
|
+
}
|
|
182
|
+
setHandler(key, handler) {
|
|
183
|
+
this._subHandlers.set({
|
|
184
|
+
...this._subHandlers(),
|
|
185
|
+
[key]: handler,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
addReactiveLogic(subscription) {
|
|
189
|
+
this._logicSubscription.add(subscription);
|
|
190
|
+
}
|
|
191
|
+
setStatus(status) {
|
|
192
|
+
this._status.set(status);
|
|
193
|
+
}
|
|
194
|
+
setErrorMessage(message) {
|
|
195
|
+
this._errorMessage.set(message);
|
|
196
|
+
}
|
|
197
|
+
ngOnDestroy() {
|
|
198
|
+
this._logicSubscription.unsubscribe();
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Registers a subform into the main form tree and coordinates handler execution.
|
|
202
|
+
*
|
|
203
|
+
* IMPORTANT:
|
|
204
|
+
* - Handler execution is gated until all registered subhandlers are ready.
|
|
205
|
+
* - Calling order matters; this is lifecycle-sensitive orchestration logic.
|
|
206
|
+
*/
|
|
207
|
+
onSubformReady(subform, groupName, nestGroups = false) {
|
|
208
|
+
if (nestGroups) {
|
|
209
|
+
this.form().setControl(groupName, subform);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
const keys = Object.keys(subform.controls);
|
|
213
|
+
keys.forEach((key) => {
|
|
214
|
+
this.form().setControl(key, subform.get(key));
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
// Prevent duplicate main handler execution when all subhandlers already resolved
|
|
218
|
+
if (this._allHandlersLoaded()) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const subformHandler = this.subHandlers()[groupName];
|
|
222
|
+
if (subformHandler) {
|
|
223
|
+
this._loadedHandlers.set(this._loadedHandlers() + 1);
|
|
224
|
+
this.addReactiveLogic(subformHandler.getReactiveLogic());
|
|
225
|
+
}
|
|
226
|
+
const totalSubHandlers = Object.keys(this.subHandlers()).length;
|
|
227
|
+
const allSubHandlersLoaded = this._loadedHandlers() === totalSubHandlers;
|
|
228
|
+
if (allSubHandlersLoaded && this.mainHandler()) {
|
|
229
|
+
this.loadMainHandler();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
loadMainHandler() {
|
|
233
|
+
if (!this.mainHandler())
|
|
234
|
+
return;
|
|
235
|
+
this.addReactiveLogic(this.mainHandler().getReactiveLogic(this.form()));
|
|
236
|
+
this._allHandlersLoaded.set(true);
|
|
237
|
+
}
|
|
238
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: FormOrchestratorBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
239
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.20", type: FormOrchestratorBase, isStandalone: true, ngImport: i0 });
|
|
240
|
+
}
|
|
241
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: FormOrchestratorBase, decorators: [{
|
|
242
|
+
type: Directive
|
|
243
|
+
}] });
|
|
244
|
+
|
|
245
|
+
/*
|
|
246
|
+
* Public API Surface of core
|
|
247
|
+
*/
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Generated bundle index. Do not edit.
|
|
251
|
+
*/
|
|
252
|
+
|
|
253
|
+
export { FormControlBase, FormHandlerBase, FormMapperBase, FormOrchestratorBase };
|
|
254
|
+
//# sourceMappingURL=ng-modular-forms-core.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ng-modular-forms-core.mjs","sources":["../../../projects/core/src/lib/form-control-base.ts","../../../projects/core/src/lib/form.util.ts","../../../projects/core/src/lib/form-handler-base.ts","../../../projects/core/src/lib/form-mapper-base.ts","../../../projects/core/src/lib/form-orchestrator-base.ts","../../../projects/core/src/public-api.ts","../../../projects/core/src/ng-modular-forms-core.ts"],"sourcesContent":["import {\n Directive,\n ElementRef,\n Input,\n HostBinding,\n Optional,\n Self,\n inject,\n ChangeDetectorRef,\n} from '@angular/core';\nimport { ControlValueAccessor, NgControl } from '@angular/forms';\n\n/**\n * Base implementation for custom form controls that integrate with Angular Reactive Forms (ControlValueAccessor)\n *\n * NOTE: This class is UI-layer only and should not be used in form orchestration logic.\n */\n\n@Directive()\nexport abstract class FormControlBase<T> implements ControlValueAccessor {\n protected readonly cdr = inject(ChangeDetectorRef);\n\n @Input() name!: string;\n @Input() placeholder: string = '';\n @Input() required = false;\n @Input() disabled = false;\n @Input() readonly = false;\n\n @Input() formControlName?: string;\n\n static nextId = 0;\n\n @HostBinding()\n id = `nmf-form-control-${FormControlBase.nextId++}`;\n\n _value: T | null = null;\n\n get value(): T | null {\n return this._value;\n }\n\n set value(val: T | null) {\n this._value = val;\n }\n\n onChange = (_value: T) => {};\n onTouched = () => {};\n\n constructor(\n @Optional() @Self() public ngControl: NgControl,\n protected elementRef: ElementRef<HTMLElement>,\n ) {\n if (this.ngControl != null) {\n this.ngControl.valueAccessor = this;\n }\n }\n\n writeValue(value: T): void {\n this._value = value;\n this.cdr.markForCheck();\n }\n\n registerOnChange(fn: (value: T) => void): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n}\n","import { FormControl, FormGroup } from '@angular/forms';\n\nexport function getControl<T = unknown>(controlName: string, form: FormGroup) {\n if (!form) {\n throw new Error(\n `Missing form instance while getting the control of \"${controlName}\"`,\n );\n }\n\n const control = form.get(controlName) as FormControl<T>;\n if (!control) {\n throw new Error(`Missing control \"${controlName}\" in form \"${form}\"`);\n }\n\n return control;\n}\n\nexport function getControlValue<T = unknown>(\n controlName: string,\n form: FormGroup,\n): T | null;\n\nexport function getControlValue(\n controlName: string,\n form: FormGroup,\n): number | null;\n\nexport function getControlValue<T = unknown>(\n controlName: string,\n form: FormGroup,\n): T | null {\n const control = getControl<T>(controlName, form);\n if (!control) {\n throw new Error(`Missing control \"${controlName}\" in form \"${form}\"`);\n }\n\n const value = control.getRawValue();\n if (value === '') {\n return null;\n }\n\n if (typeof value === 'string') {\n const cleaned = value.replace(/,/g, '');\n\n if (!Number.isNaN(Number(cleaned))) {\n return parseCurrency(value) as T;\n }\n }\n\n return value;\n}\n\nexport function parseCurrency(\n currencyString: string | number | null | undefined,\n): number {\n if (currencyString == null) {\n return 0;\n }\n\n if (typeof currencyString === 'number') {\n return currencyString;\n }\n\n return Number(currencyString.replace(/[^0-9.-]/g, ''));\n}\n","import { FormControl, FormGroup } from '@angular/forms';\nimport { Observable, Subscription } from 'rxjs';\nimport { getControl } from './form.util';\n\nexport abstract class FormHandlerBase<ControlNames extends string = string> {\n abstract getReactiveLogic(form?: FormGroup): Subscription;\n\n protected registeredControls: Record<string, FormControl> = {};\n\n /**\n * Registers form controls for later reactive access.\n */\n registerControls(form: FormGroup, controlNames: ControlNames[]): void {\n controlNames.forEach((cn) => {\n const control = getControl(cn.replace(/_/g, '.'), form);\n\n if (!control) {\n throw new Error(\n `Failed to register control with name: \"${cn}\" in form group: \"${form}\"`,\n );\n }\n\n const key = cn as ControlNames;\n this.registeredControls[key] = control;\n });\n }\n\n valueChangesOf<T>(key: ControlNames): Observable<T> {\n if (!this.registeredControls[key]) {\n throw new Error(\n `Control with name: \"${key}\" not found. Ensure it is registered in registerControls(...)`,\n );\n }\n\n return this.registeredControls[key].valueChanges as Observable<T>;\n }\n}\n","import { FormGroup } from '@angular/forms';\n\nexport abstract class FormMapperBase<\n TModelIn = unknown,\n TModelOut = unknown,\n TFormModel = unknown,\n TStore = Record<string, unknown>,\n> {\n /**\n * Maps form state + external store into an API request payload.\n */\n abstract buildRequest(form: FormGroup, store: TStore): Partial<TModelOut>;\n\n /**\n * Transforms a domain/API model into a form-compatible structure.\n */\n abstract transformFromModel(model: TModelIn): TFormModel;\n\n patchForm(\n form: FormGroup,\n data: TFormModel,\n _context: TStore,\n emitEvent: boolean = false,\n ): void {\n form.patchValue(\n {\n ...(data as Record<string, unknown>),\n },\n { emitEvent },\n );\n }\n\n patchFromModel(form: FormGroup, model: TModelIn, store: TStore): void {\n const formModel = this.transformFromModel(model);\n this.patchForm(form, formModel, store);\n }\n}\n","import { FormGroup } from '@angular/forms';\nimport { Directive, OnDestroy, signal } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport { FormHandlerBase } from './form-handler-base';\n\nexport type FormStatus = 'idle' | 'submitting' | 'error' | 'success';\n\ntype FormHandlerRegistry = Record<string, FormHandlerBase<string>>;\n\ninterface FormOrchestratorOptions {\n mainHandler?: FormHandlerBase<string> | null;\n subHandlers?: FormHandlerRegistry;\n}\n\n@Directive()\nexport abstract class FormOrchestratorBase implements OnDestroy {\n private _form = signal<FormGroup>(new FormGroup({}));\n\n private _mainHandler = signal<FormHandlerBase<string> | null>(null);\n private _subHandlers = signal<FormHandlerRegistry>({});\n\n private _status = signal<FormStatus>('idle');\n private _errorMessage = signal<string | null>(null);\n\n private _loadedHandlers = signal<number>(0);\n private _allHandlersLoaded = signal<boolean>(false);\n\n private _logicSubscription = new Subscription();\n\n public readonly form = this._form.asReadonly();\n public readonly mainHandler = this._mainHandler.asReadonly();\n public readonly subHandlers = this._subHandlers.asReadonly();\n public readonly status = this._status.asReadonly();\n public readonly errorMessage = this._errorMessage.asReadonly();\n\n /**\n * Initializes orchestration state.\n * Must be called before any subform registration or handler execution.\n */\n public initialize(\n form: FormGroup,\n formOrchestratorOptions?: FormOrchestratorOptions,\n ) {\n const { mainHandler, subHandlers = {} } = formOrchestratorOptions ?? {};\n\n this._form.set(form);\n this._mainHandler.set(mainHandler ?? null);\n this._subHandlers.set(subHandlers);\n this._loadedHandlers.set(0);\n\n const subHandlerCount = Object.keys(this.subHandlers());\n if (subHandlerCount.length === 0) {\n this.loadMainHandler();\n }\n }\n\n public setForm(form: FormGroup) {\n this._form.set(form);\n }\n\n public getHandler(key: string): FormHandlerBase<string> | undefined {\n return this.subHandlers()[key];\n }\n\n public setHandler(key: string, handler: FormHandlerBase<string>) {\n this._subHandlers.set({\n ...this._subHandlers(),\n [key]: handler,\n });\n }\n\n public addReactiveLogic(subscription: Subscription) {\n this._logicSubscription.add(subscription);\n }\n\n public setStatus(status: FormStatus) {\n this._status.set(status);\n }\n\n public setErrorMessage(message: string | null) {\n this._errorMessage.set(message);\n }\n\n ngOnDestroy(): void {\n this._logicSubscription.unsubscribe();\n }\n\n /**\n * Registers a subform into the main form tree and coordinates handler execution.\n *\n * IMPORTANT:\n * - Handler execution is gated until all registered subhandlers are ready.\n * - Calling order matters; this is lifecycle-sensitive orchestration logic.\n */\n onSubformReady(\n subform: FormGroup,\n groupName: string,\n nestGroups: boolean = false,\n ): void {\n if (nestGroups) {\n this.form().setControl(groupName, subform);\n } else {\n const keys = Object.keys(subform.controls);\n keys.forEach((key) => {\n this.form().setControl(key, subform.get(key));\n });\n }\n\n // Prevent duplicate main handler execution when all subhandlers already resolved\n if (this._allHandlersLoaded()) {\n return;\n }\n\n const subformHandler = this.subHandlers()[groupName];\n\n if (subformHandler) {\n this._loadedHandlers.set(this._loadedHandlers() + 1);\n this.addReactiveLogic(subformHandler.getReactiveLogic());\n }\n\n const totalSubHandlers = Object.keys(this.subHandlers()).length;\n const allSubHandlersLoaded = this._loadedHandlers() === totalSubHandlers;\n\n if (allSubHandlersLoaded && this.mainHandler()) {\n this.loadMainHandler();\n }\n }\n\n private loadMainHandler() {\n if (!this.mainHandler()) return;\n\n this.addReactiveLogic(this.mainHandler()!.getReactiveLogic(this.form()));\n this._allHandlersLoaded.set(true);\n }\n}\n","/*\n * Public API Surface of core\n */\n\nexport * from './lib/index';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ElementRef, ChangeDetectorRef } from '@angular/core';
|
|
2
|
+
import { ControlValueAccessor, NgControl } from '@angular/forms';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
/**
|
|
5
|
+
* Base implementation for custom form controls that integrate with Angular Reactive Forms (ControlValueAccessor)
|
|
6
|
+
*
|
|
7
|
+
* NOTE: This class is UI-layer only and should not be used in form orchestration logic.
|
|
8
|
+
*/
|
|
9
|
+
export declare abstract class FormControlBase<T> implements ControlValueAccessor {
|
|
10
|
+
ngControl: NgControl;
|
|
11
|
+
protected elementRef: ElementRef<HTMLElement>;
|
|
12
|
+
protected readonly cdr: ChangeDetectorRef;
|
|
13
|
+
name: string;
|
|
14
|
+
placeholder: string;
|
|
15
|
+
required: boolean;
|
|
16
|
+
disabled: boolean;
|
|
17
|
+
readonly: boolean;
|
|
18
|
+
formControlName?: string;
|
|
19
|
+
static nextId: number;
|
|
20
|
+
id: string;
|
|
21
|
+
_value: T | null;
|
|
22
|
+
get value(): T | null;
|
|
23
|
+
set value(val: T | null);
|
|
24
|
+
onChange: (_value: T) => void;
|
|
25
|
+
onTouched: () => void;
|
|
26
|
+
constructor(ngControl: NgControl, elementRef: ElementRef<HTMLElement>);
|
|
27
|
+
writeValue(value: T): void;
|
|
28
|
+
registerOnChange(fn: (value: T) => void): void;
|
|
29
|
+
registerOnTouched(fn: () => void): void;
|
|
30
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<FormControlBase<any>, [{ optional: true; self: true; }, null]>;
|
|
31
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<FormControlBase<any>, never, never, { "name": { "alias": "name"; "required": false; }; "placeholder": { "alias": "placeholder"; "required": false; }; "required": { "alias": "required"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "readonly": { "alias": "readonly"; "required": false; }; "formControlName": { "alias": "formControlName"; "required": false; }; }, {}, never, never, true, never>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FormControl, FormGroup } from '@angular/forms';
|
|
2
|
+
import { Observable, Subscription } from 'rxjs';
|
|
3
|
+
export declare abstract class FormHandlerBase<ControlNames extends string = string> {
|
|
4
|
+
abstract getReactiveLogic(form?: FormGroup): Subscription;
|
|
5
|
+
protected registeredControls: Record<string, FormControl>;
|
|
6
|
+
/**
|
|
7
|
+
* Registers form controls for later reactive access.
|
|
8
|
+
*/
|
|
9
|
+
registerControls(form: FormGroup, controlNames: ControlNames[]): void;
|
|
10
|
+
valueChangesOf<T>(key: ControlNames): Observable<T>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FormGroup } from '@angular/forms';
|
|
2
|
+
export declare abstract class FormMapperBase<TModelIn = unknown, TModelOut = unknown, TFormModel = unknown, TStore = Record<string, unknown>> {
|
|
3
|
+
/**
|
|
4
|
+
* Maps form state + external store into an API request payload.
|
|
5
|
+
*/
|
|
6
|
+
abstract buildRequest(form: FormGroup, store: TStore): Partial<TModelOut>;
|
|
7
|
+
/**
|
|
8
|
+
* Transforms a domain/API model into a form-compatible structure.
|
|
9
|
+
*/
|
|
10
|
+
abstract transformFromModel(model: TModelIn): TFormModel;
|
|
11
|
+
patchForm(form: FormGroup, data: TFormModel, _context: TStore, emitEvent?: boolean): void;
|
|
12
|
+
patchFromModel(form: FormGroup, model: TModelIn, store: TStore): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { FormGroup } from '@angular/forms';
|
|
2
|
+
import { OnDestroy } from '@angular/core';
|
|
3
|
+
import { Subscription } from 'rxjs';
|
|
4
|
+
import { FormHandlerBase } from './form-handler-base';
|
|
5
|
+
import * as i0 from "@angular/core";
|
|
6
|
+
export type FormStatus = 'idle' | 'submitting' | 'error' | 'success';
|
|
7
|
+
type FormHandlerRegistry = Record<string, FormHandlerBase<string>>;
|
|
8
|
+
interface FormOrchestratorOptions {
|
|
9
|
+
mainHandler?: FormHandlerBase<string> | null;
|
|
10
|
+
subHandlers?: FormHandlerRegistry;
|
|
11
|
+
}
|
|
12
|
+
export declare abstract class FormOrchestratorBase implements OnDestroy {
|
|
13
|
+
private _form;
|
|
14
|
+
private _mainHandler;
|
|
15
|
+
private _subHandlers;
|
|
16
|
+
private _status;
|
|
17
|
+
private _errorMessage;
|
|
18
|
+
private _loadedHandlers;
|
|
19
|
+
private _allHandlersLoaded;
|
|
20
|
+
private _logicSubscription;
|
|
21
|
+
readonly form: import("@angular/core").Signal<FormGroup<any>>;
|
|
22
|
+
readonly mainHandler: import("@angular/core").Signal<FormHandlerBase<string> | null>;
|
|
23
|
+
readonly subHandlers: import("@angular/core").Signal<FormHandlerRegistry>;
|
|
24
|
+
readonly status: import("@angular/core").Signal<FormStatus>;
|
|
25
|
+
readonly errorMessage: import("@angular/core").Signal<string | null>;
|
|
26
|
+
/**
|
|
27
|
+
* Initializes orchestration state.
|
|
28
|
+
* Must be called before any subform registration or handler execution.
|
|
29
|
+
*/
|
|
30
|
+
initialize(form: FormGroup, formOrchestratorOptions?: FormOrchestratorOptions): void;
|
|
31
|
+
setForm(form: FormGroup): void;
|
|
32
|
+
getHandler(key: string): FormHandlerBase<string> | undefined;
|
|
33
|
+
setHandler(key: string, handler: FormHandlerBase<string>): void;
|
|
34
|
+
addReactiveLogic(subscription: Subscription): void;
|
|
35
|
+
setStatus(status: FormStatus): void;
|
|
36
|
+
setErrorMessage(message: string | null): void;
|
|
37
|
+
ngOnDestroy(): void;
|
|
38
|
+
/**
|
|
39
|
+
* Registers a subform into the main form tree and coordinates handler execution.
|
|
40
|
+
*
|
|
41
|
+
* IMPORTANT:
|
|
42
|
+
* - Handler execution is gated until all registered subhandlers are ready.
|
|
43
|
+
* - Calling order matters; this is lifecycle-sensitive orchestration logic.
|
|
44
|
+
*/
|
|
45
|
+
onSubformReady(subform: FormGroup, groupName: string, nestGroups?: boolean): void;
|
|
46
|
+
private loadMainHandler;
|
|
47
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<FormOrchestratorBase, never>;
|
|
48
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<FormOrchestratorBase, never, never, {}, {}, never, never, true, never>;
|
|
49
|
+
}
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { FormControl, FormGroup } from '@angular/forms';
|
|
2
|
+
export declare function getControl<T = unknown>(controlName: string, form: FormGroup): FormControl<T>;
|
|
3
|
+
export declare function getControlValue<T = unknown>(controlName: string, form: FormGroup): T | null;
|
|
4
|
+
export declare function getControlValue(controlName: string, form: FormGroup): number | null;
|
|
5
|
+
export declare function parseCurrency(currencyString: string | number | null | undefined): number;
|
package/lib/index.d.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ng-modular-forms/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"peerDependencies": {
|
|
5
|
+
"@angular/core": ">=17 <22",
|
|
6
|
+
"@angular/common": ">=17 <22",
|
|
7
|
+
"@angular/forms": ">=17 <22",
|
|
8
|
+
"rxjs": ">=7.8.0 <8"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"tslib": "^2.6.0"
|
|
12
|
+
},
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"module": "fesm2022/ng-modular-forms-core.mjs",
|
|
15
|
+
"typings": "index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
"./package.json": {
|
|
18
|
+
"default": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./index.d.ts",
|
|
22
|
+
"default": "./fesm2022/ng-modular-forms-core.mjs"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
package/public-api.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib/index';
|