@ng-modular-forms/core 0.5.1 → 0.6.1

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 CHANGED
@@ -1,12 +1,6 @@
1
1
  # @ng-modular-forms/core
2
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
3
+ Core primitives, behaviors, and input components for orchestrating complex Angular reactive forms.
10
4
 
11
5
  ## Installation
12
6
 
@@ -14,31 +8,38 @@ Core primitives for orchestrating complex Angular reactive forms.
14
8
  npm install @ng-modular-forms/core
15
9
  ```
16
10
 
11
+ There is also an optional package which supports Angular Material UI components:
12
+
13
+ ```bash
14
+ npm install @ng-modular-forms/material
15
+ ```
16
+
17
17
  ## Key Concepts
18
18
 
19
- ### FormOrchestratorBase
19
+ ### FormOrchestrator
20
20
 
21
21
  Coordinates form structure and lifecycle.
22
22
 
23
23
  ```ts
24
24
  @Component({...})
25
- export class ExampleComponent extends FormOrchestratorBase {
25
+ export class ExampleComponent extends FormOrchestrator {
26
26
 
27
- ngOnInit() {
28
- // Handlers are not required if there is no reactive logic or value change subscriptions.
29
- const mainHandler = inject(ExampleFormHandler);
30
- const sectionAHandler = inject(SectionAHandler);
27
+ constructor(
28
+ override readonly hydrator: FormHydrator,
29
+ override readonly serializer: FormSerializer,
30
+ ) {
31
+ super(hydrator, serializer);
31
32
 
32
- form = new FormGroup({});
33
- handlers = [mainHandler, sectionAHandler]
33
+ const sectionAHandler = inject(SectionAHandler);
34
34
 
35
- this.initialize(form, handlers);
35
+ form = new FormGroup({});
36
+ handlers = [sectionAHandler]
37
+
38
+ this.initialize({ form, handlers });
36
39
  }
37
40
  }
38
41
  ```
39
42
 
40
- ---
41
-
42
43
  ### FormHandlerBase
43
44
 
44
45
  Encapsulates reactive logic.
@@ -53,6 +54,14 @@ export class SectionAHandler extends FormHandlerBase<ControlNames> {
53
54
  override getReactiveLogic(form?: FormGroup): Subscription {
54
55
  this.registerControls(form, [...CONTROL_NAMES]);
55
56
 
57
+ const sub = new Subscription();
58
+
59
+ sub.add(this.reactiveMethod());
60
+
61
+ return sub;
62
+ }
63
+
64
+ private reactiveMethod() {
56
65
  return this.valueChangesOf("fieldA").subscribe((value) => {
57
66
  if (value) {
58
67
  this.controls.dependentField.enable();
@@ -64,22 +73,21 @@ export class SectionAHandler extends FormHandlerBase<ControlNames> {
64
73
  }
65
74
  ```
66
75
 
67
- ---
68
-
69
76
  ### FormMapperBase
70
77
 
71
- Handles transformations between API and form.
78
+ Handles transformations between API and form. Optional: `FormHydrator` and `FormSerializer` will automatically map correlated values.
72
79
 
73
80
  ```ts
74
- export class ExampleMapper extends FormMapperBase<ApiModel, RequestModel> {
75
- buildRequest(form: FormGroup) {
81
+ export class ExampleMapper extends FormMapperBase<ApiModel, RequestModel, FormModel> {
82
+ toRequest(form: FormGroup): RequestModel {
83
+ const fieldA = form.value.fieldA ?? null;
76
84
  return {
77
- fieldA: form.value.fieldA,
85
+ fieldA: fieldA?.replace(/_/g, " "),
78
86
  fieldB: form.value.fieldB,
79
87
  };
80
88
  }
81
89
 
82
- transformFromModel(model: ApiModel) {
90
+ fromModel(model: ApiModel): FormModel {
83
91
  return {
84
92
  fieldA: model.fieldA,
85
93
  fieldB: model.fieldB,
@@ -88,27 +96,85 @@ export class ExampleMapper extends FormMapperBase<ApiModel, RequestModel> {
88
96
  }
89
97
  ```
90
98
 
91
- ---
99
+ ### FormControlBase
100
+
101
+ Provides the ControlValueAccessor implementation as well as common component inputs such as label, placeholder, etc.
102
+
103
+ ```ts
104
+ @Component({
105
+ template: `
106
+ <input
107
+ [value]="displayValue()"
108
+ [disabled]="disabled()"
109
+ [required]="isRequired()"
110
+ (blur)="onTouched()"
111
+ (input)="onInput($event)" />
112
+ `,
113
+ })
114
+ export class CustomInput extends FormControlBase<string | null> {
115
+ displayValue = signal<string | null>(null);
116
+
117
+ override writeValue(value: number | null): void {
118
+ super.writeValue(value);
119
+ this.displayValue.set(value != null ? formatNumber(value) : null);
120
+ }
121
+
122
+ onInput(event: Event) {
123
+ if (this._disabled()) return;
124
+
125
+ const rawValue: string | null = (event.target as HTMLInputElement).value ?? null;
126
+ const value: number = parseNumber(rawValue);
127
+
128
+ this.displayValue.set(value != null ? formatNumber(value) : null);
129
+
130
+ this.onChange(value);
131
+ }
132
+ }
133
+ ```
134
+
135
+ ### Reusable Input Behaviors
136
+
137
+ Behaviors are just plain JavaScript objects:
138
+
139
+ ```ts
140
+ export class CurrencyBehavior {
141
+ blockNonDigitKey(event: KeyboardEvent) {
142
+ const input = event.target as HTMLInputElement;
143
+ const value = input.value;
144
+
145
+ if (event.ctrlKey || event.metaKey) {
146
+ return;
147
+ }
92
148
 
93
- ### Responsibility Boundaries
149
+ // Custom logic...
94
150
 
95
- | Layer | Responsibility |
96
- | :--------------- | :------------------------------------------------------- |
97
- | **Orchestrator** | Manages form composition and lifecycle coordination. |
98
- | **Handler** | Encapsulates all reactive logic and stream management. |
99
- | **Mapper** | Handles data transformation between API and Form states. |
151
+ event.preventDefault();
152
+ }
153
+ }
154
+ ```
100
155
 
101
- ---
156
+ ## Available Input Components
102
157
 
103
- ### No UI Included
158
+ All inputs share a consistent API and can be swapped between native and Material implementations without changing form logic.
104
159
 
105
- This package does **not** provide UI components.
160
+ | Input Type | Native Selector | Material Selector | Description |
161
+ |-----------------|--------------------------------|------------------------------------|----------------------------------------------------------------------------------|
162
+ | Text / Password | `nmf-text` | `nmf-mat-text` | Supports multiple input types including password with visibility toggle |
163
+ | Number | `nmf-number` | `nmf-mat-number` | Numeric input with type-safe value handling |
164
+ | Currency | `nmf-currency` | `nmf-mat-currency` | Formatted currency input with parsing and display formatting |
165
+ | Date | `nmf-datepicker` | `nmf-mat-datepicker` | Date selection with native or Angular Material datepicker UI |
166
+ | Time | `nmf-timepicker` | `nmf-mat-timepicker` | Time input with structured formatting |
167
+ | Select | `nmf-select` | `nmf-mat-select` | Dropdown/select with support for disabled options |
168
+ | Textarea | `nmf-textarea` | `nmf-mat-textarea` | Multi-line text input with configurable rows |
106
169
 
107
- Use:
170
+ ### Shared Features
108
171
 
109
- - @ng-modular-forms/behavior
110
- - @ng-modular-forms/input
111
- - @ng-modular-forms/material
172
+ - Implements `ControlValueAccessor`
173
+ - Fully compatible with Angular Reactive Forms
174
+ - Consistent API across all inputs
175
+ - Built-in validation state + error messaging
176
+ - Label, required indicator, and loading state support
177
+ - Behavior-driven input handling (formatting, parsing, restrictions)
112
178
 
113
179
  ## License
114
180