@ng-simplicity/forms-core 1.0.0 → 1.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 CHANGED
@@ -1,6 +1,10 @@
1
1
  # @ng-simplicity/forms-core
2
2
 
3
- The core engine package for **NG-Simplicity Forms**. This package manages form states, dynamic registry injection, component lifecycle bindings, and structural layout directives (Rows, Columns, Sections, Groups, Arrays).
3
+ The core engine package for **NG-Simplicity Forms**. This package manages form states, dynamic registry injection, component lifecycle bindings, and structural layout directives (Rows, Columns, Sections, Groups, Arrays). It enables you to construct highly dynamic, complex forms where control properties (such as visibility, enabled/disabled state, select options, and validation rules) reactively adapt in real-time based on the current values inside the form. The engine is highly customizable, allowing you to develop and add your own custom form controls to the registry to be used in the form rendering.
4
+
5
+ This core package is extended by styling integration packages that provide prebuilt themed form components:
6
+ - **[@ng-simplicity/forms-bootstrap](../forms-bootstrap/README.md)**: Dynamic forms pre-styled with Bootstrap form fields and layouts.
7
+ - **[@ng-simplicity/forms-material](../forms-material/README.md)**: Dynamic forms pre-styled with Angular Material inputs and controls.
4
8
 
5
9
  ---
6
10
 
@@ -96,40 +100,84 @@ export class CustomFancyInputComponent
96
100
  }
97
101
  ```
98
102
 
99
- ### 2. Register Your Component
103
+ ### 2. Register, Construct, and Set Form Config
104
+
105
+ To use the dynamic form in your application:
106
+ 1. Provide the `NgsFormsService` at the component level (so that each page/form gets its own isolated instance of the form service).
107
+ 2. Register your custom components with `NgsFormsComponentRegistryService`.
108
+ 3. Construct your layout configuration (`NgsFormsFormConfig`).
109
+ 4. Set the configuration on `NgsFormsService`.
110
+ 5. Include the `<ngs-form></ngs-form>` tag in your component template.
100
111
 
101
- Register it into the registry service:
112
+ Here is a complete setup:
102
113
 
103
114
  ```typescript
104
- import { Component } from '@angular/core';
105
- import { NgsFormsComponentRegistryService } from '@ng-simplicity/forms-core';
115
+ import { Component, OnInit, inject } from '@angular/core';
116
+ import { Validators } from '@angular/forms';
117
+ import {
118
+ NgsFormComponent,
119
+ NgsFormsComponentRegistryService,
120
+ NgsFormsService,
121
+ NgsFormsFormConfig,
122
+ NgsFormsFormGroupComponent
123
+ } from '@ng-simplicity/forms-core';
106
124
  import { CustomFancyInputComponent } from './custom-fancy-input.component';
107
125
 
108
126
  @Component({
109
127
  selector: 'app-root',
110
- template: '<ngs-form></ngs-form>'
128
+ standalone: true,
129
+ imports: [NgsFormComponent],
130
+ providers: [NgsFormsService],
131
+ template: `
132
+ <div class="form-container">
133
+ <h2>Profile Settings</h2>
134
+ <ngs-form></ngs-form>
135
+ <button (click)="onSubmit()">Submit</button>
136
+ </div>
137
+ `
111
138
  })
112
- export class AppComponent {
139
+ export class AppComponent implements OnInit {
140
+ private ngsFormsService = inject(NgsFormsService);
141
+
113
142
  constructor(registry: NgsFormsComponentRegistryService) {
143
+ // Register your custom components with the dynamic engine
114
144
  registry.register(CustomFancyInputComponent.key, CustomFancyInputComponent);
115
145
  }
116
- }
117
- ```
118
146
 
119
- ### 3. Add to Schema
147
+ ngOnInit() {
148
+ // Construct the schema configuration
149
+ const formConfig: NgsFormsFormConfig = {
150
+ inputUpdateDebounce: 100,
151
+ root: NgsFormsFormGroupComponent.create({
152
+ name: 'profileForm',
153
+ items: [
154
+ CustomFancyInputComponent.create({
155
+ name: 'nickname',
156
+ label: 'Nickname',
157
+ placeholder: 'Enter cool nickname...',
158
+ customColor: 'purple',
159
+ validators: [Validators.required, Validators.minLength(3)],
160
+ errorMessageMap: {
161
+ required: 'Nickname is required.',
162
+ minlength: 'Nickname must be at least 3 characters.'
163
+ }
164
+ })
165
+ ]
166
+ })
167
+ };
120
168
 
121
- ```typescript
122
- const formConfig = {
123
- inputUpdateDebounce: 100,
124
- root: NgsFormsFormGroupComponent.create({ name: 'profileForm' }, [
125
- CustomFancyInputComponent.create({
126
- name: 'nickname',
127
- label: 'Nickname',
128
- placeholder: 'Enter cool nickname...',
129
- customColor: 'purple'
130
- })
131
- ])
132
- };
169
+ // Set the configuration to initialize the form control tree
170
+ this.ngsFormsService.setFormConfig(formConfig);
171
+ }
172
+
173
+ onSubmit() {
174
+ if (this.ngsFormsService.isValid) {
175
+ console.log('Form Submitted!', this.ngsFormsService.formValue);
176
+ } else {
177
+ this.ngsFormsService.setIsSubmitted(true); // Triggers error validation display
178
+ }
179
+ }
180
+ }
133
181
  ```
134
182
 
135
183
  ---
@@ -151,6 +199,274 @@ Each form control component that extends `NgsFormsFormItemWithVisibleAndValidato
151
199
 
152
200
  ---
153
201
 
202
+ ## Common Control Properties
203
+
204
+ Most controls and layout structures support a standard set of core properties that are managed by the dynamic forms engine. These properties allow passing either a static/basic value or an asynchronous stream (`Observable`):
205
+
206
+ 1. **Visibility**:
207
+ - `visible` (boolean): Controls whether the item is mounted in the DOM.
208
+ - `visible$` (`Observable<boolean>`): Stream version to dynamically show/hide components.
209
+ - *Note: Visibility is configured at the inner config level (`NgsFormsFormItemConfigBase`).*
210
+
211
+ 2. **Disabled Status**:
212
+ - `disabled` (boolean): Sets the initial disabled state of the form control.
213
+ - `disabled$` (`Observable<boolean>`): Stream to toggle disabled state.
214
+ - *Configured at the inner `config` level.*
215
+
216
+ 3. **Validators**:
217
+ - `validators` (`Array<ValidatorFn>`): Array of standard/custom Angular Validator functions.
218
+ - `validators$` (`Observable<Array<ValidatorFn>>`): Stream of validator functions to change validators dynamically.
219
+ - *Configured at the inner `config` level.*
220
+
221
+ 4. **Options** (Only for components with choice selection like `select` and `radio`):
222
+ - `options` (`Array<NgsFormsFormInputOption>`): Static array of choices `{ id: string | number, label: string, disabled?: boolean }`.
223
+ - `options$` (`Observable<Array<NgsFormsFormInputOption>>`): Stream to dynamically populate choices.
224
+ - *Configured at the inner `config` level.*
225
+
226
+ > [!TIP]
227
+ > By supplying **Observable** streams (such as `visible$`, `disabled$`, `validators$`, or `options$`), you can build highly dynamic and reactive forms. Because these observables can be piped directly from the form service's value change stream (`this.ngsFormsService.formValue$`), fields can dynamically enable/disable, appear/disappear, change options, or update validation rules in real-time based on user input in other fields.
228
+
229
+ ---
230
+
231
+ ## Dynamic Form Arrays (`form-array`)
232
+
233
+ Form Arrays in `@ng-simplicity/forms-core` allow for dynamic lists of repeating items (such as adding multiple emergency contacts or addresses). Managing form arrays requires three distinct components working in harmony:
234
+
235
+ 1. **`NgsFormsFormArrayContainerComponent` (Container)**:
236
+ - Manages the outer `FormArray` control and acts as the shell.
237
+ - Requires setting `name` (the control name), `initialItemCount` (minimum display elements), and boundaries (`minItems`, `maxItems`).
238
+ 2. **`NgsFormsFormArrayListComponent` (Repeater)**:
239
+ - Resides inside the `items` array of the container.
240
+ - Defines a `templateItem` property containing the layout / controls to clone for each dynamic item.
241
+ - **Crucial Pattern**: The `templateItem` should wrap the inputs inside a layout structure (like a `form-row` or `column`). The engine will dynamically register these repeated fields inside a dedicated `FormGroup` *per array item* under the hood (e.g. `contacts: [{ name: '', phone: '' }]`), isolating the control states.
242
+ 3. **Array Mutation Buttons (`form-array-add-item` / `form-array-remove-item`)**:
243
+ - `NgsFormsFormArrayAddItemComponent`: Placed inside the container's items array (outside the repeater component) to trigger appending a new form group cloned from the `templateItem`.
244
+ - `NgsFormsFormArrayRemoveItemComponent`: Placed inside the `templateItem` template (next to the form inputs) so each item row contains a button to delete itself.
245
+
246
+ ### Example Schema Configuration
247
+
248
+ Here is a complete setup illustrating how these components work together:
249
+
250
+ ```typescript
251
+ import {
252
+ NgsFormsFormArrayContainerComponent,
253
+ NgsFormsFormArrayListComponent,
254
+ NgsFormsRowComponent,
255
+ NgsFormsFormArrayRemoveItemComponent,
256
+ NgsFormsFormArrayAddItemComponent
257
+ } from '@ng-simplicity/forms-core';
258
+
259
+ const contactArray = NgsFormsFormArrayContainerComponent.create({
260
+ name: 'contacts',
261
+ initialItemCount: 1,
262
+ minItems: 1,
263
+ maxItems: 3,
264
+ containerClass: 'contacts-container',
265
+ itemContainerClass: 'contact-row-card',
266
+ items: [
267
+ // 1. The Repeater Component containing the repeated schema structure
268
+ NgsFormsFormArrayListComponent.create({
269
+ templateItem: NgsFormsRowComponent.create({
270
+ containerClass: 'row align-items-center',
271
+ items: [
272
+ // Input fields per array item
273
+ SomeInputComponent.create({
274
+ name: 'contactName',
275
+ label: 'Contact Name',
276
+ validators: [Validators.required]
277
+ }),
278
+ SomeInputComponent.create({
279
+ name: 'contactPhone',
280
+ label: 'Phone Number',
281
+ validators: [Validators.required]
282
+ }),
283
+ // 2. The Remove Button placed inside the templateItem row to delete this specific row
284
+ NgsFormsFormArrayRemoveItemComponent.create({
285
+ buttonText: 'Remove',
286
+ buttonClass: 'btn btn-danger'
287
+ })
288
+ ]
289
+ })
290
+ }),
291
+
292
+ // 3. The Add Button placed outside the repeater but inside the container to append rows
293
+ NgsFormsRowComponent.create({
294
+ items: [
295
+ NgsFormsFormArrayAddItemComponent.create({
296
+ buttonText: 'Add Additional Contact',
297
+ buttonClass: 'btn btn-primary'
298
+ })
299
+ ]
300
+ })
301
+ ]
302
+ });
303
+ ```
304
+
305
+ ---
306
+
307
+ ## Core Layout & Structural Components
308
+
309
+ `@ng-simplicity/forms-core` provides all non-styling-specific components and layout containers. Here is the list of available core components and how to construct them:
310
+
311
+ ### 1. Form Group (`form-group`)
312
+ Creates a nested `FormGroup` control.
313
+ - **Component**: `NgsFormsFormGroupComponent`
314
+ - **Key**: `'form-group'`
315
+ - **Config Type**: `NgsFormsFormItemConfigBaseItemWithNameAndValidators`
316
+
317
+ ```typescript
318
+ import { NgsFormsFormGroupComponent } from '@ng-simplicity/forms-core';
319
+
320
+ const group = NgsFormsFormGroupComponent.create({
321
+ name: 'profileDetails',
322
+ disabled: false,
323
+ items: [
324
+ // Array of child NgsFormsFormItem<any> components
325
+ ]
326
+ });
327
+ ```
328
+
329
+ ### 2. Form Array Container (`form-array`)
330
+ Creates a dynamic `FormArray` containing a list of repeated controls/groups.
331
+ - **Component**: `NgsFormsFormArrayContainerComponent`
332
+ - **Key**: `'form-array'`
333
+ - **Config Type**: `NgsFormItemArrayConfig`
334
+
335
+ ```typescript
336
+ import { NgsFormsFormArrayContainerComponent } from '@ng-simplicity/forms-core';
337
+
338
+ const contactList = NgsFormsFormArrayContainerComponent.create({
339
+ name: 'contacts',
340
+ initialItemCount: 1, // Number of items shown initially
341
+ minItems: 1, // Minimum allowed items
342
+ maxItems: 5, // Maximum allowed items
343
+ containerClass: 'custom-array-class', // CSS class for array container
344
+ itemContainerClass: 'custom-array-item-class', // CSS class for each item container
345
+ items: [
346
+ // Template items to replicate (e.g. input-text controls followed by a form-array-remove-button)
347
+ ]
348
+ });
349
+ ```
350
+
351
+ ### 3. Form Array Add Item Button (`form-array-add-item`)
352
+ Button to dynamically append a new item into the parent FormArray.
353
+ - **Component**: `NgsFormsFormArrayAddItemComponent`
354
+ - **Key**: `'form-array-add-item'`
355
+ - **Config Type**: `NgsFormItemArrayAddItemConfig`
356
+
357
+ ```typescript
358
+ import { NgsFormsFormArrayAddItemComponent } from '@ng-simplicity/forms-core';
359
+
360
+ const addContactBtn = NgsFormsFormArrayAddItemComponent.create({
361
+ buttonText: 'Add Contact',
362
+ buttonClass: 'btn btn-success',
363
+ buttonIcon: 'plus-icon'
364
+ });
365
+ ```
366
+
367
+ ### 4. Form Array Remove Item Button (`form-array-remove-item`)
368
+ Button to dynamically remove the current item from the parent FormArray.
369
+ - **Component**: `NgsFormsFormArrayRemoveItemComponent`
370
+ - **Key**: `'form-array-remove-item'`
371
+ - **Config Type**: `NgsFormItemArrayRemoveItemConfig`
372
+
373
+ ```typescript
374
+ import { NgsFormsFormArrayRemoveItemComponent } from '@ng-simplicity/forms-core';
375
+
376
+ const removeContactBtn = NgsFormsFormArrayRemoveItemComponent.create({
377
+ buttonText: 'Delete',
378
+ buttonClass: 'btn btn-danger',
379
+ buttonIcon: 'trash-icon'
380
+ });
381
+ ```
382
+
383
+ ### 5. Section (`section`)
384
+ A visual layout grouping with a title and subtitle.
385
+ - **Component**: `NgsFormsFormSectionComponent`
386
+ - **Key**: `'section'`
387
+ - **Config Type**: `NgsFormsFormSectionConfig`
388
+
389
+ ```typescript
390
+ import { NgsFormsFormSectionComponent } from '@ng-simplicity/forms-core';
391
+
392
+ const personalSection = NgsFormsFormSectionComponent.create({
393
+ title: 'Personal Information',
394
+ titleClass: 'custom-title-class', // Optional, defaults to 'h3'
395
+ subtitle: 'Tell us a bit about yourself',
396
+ subtitleClass: 'custom-subtitle-class', // Optional, defaults to 'lead'
397
+ items: [
398
+ // Array of child NgsFormsFormItem<any> components
399
+ ]
400
+ });
401
+ ```
402
+
403
+ ### 6. Row Layout (`form-row`)
404
+ Arranges child elements in a flex grid row.
405
+ - **Component**: `NgsFormsRowComponent`
406
+ - **Key**: `'form-row'`
407
+ - **Config Type**: `NgsFormItemRowConfig`
408
+
409
+ ```typescript
410
+ import { NgsFormsRowComponent } from '@ng-simplicity/forms-core';
411
+
412
+ const flexRow = NgsFormsRowComponent.create({
413
+ containerClass: 'row g-3', // CSS class for row container
414
+ columnClass: 'col-md-6', // CSS class applied to ALL child columns (optional)
415
+ columnClasses: ['col-md-8', 'col-md-4'], // CSS classes applied to each column individually (optional)
416
+ items: [
417
+ // Array of child NgsFormsFormItem<any> components
418
+ ]
419
+ });
420
+ ```
421
+
422
+ ### 7. Column Layout (`column`)
423
+ Groups elements vertically inside a layout.
424
+ - **Component**: `NgsFormsColumnComponent`
425
+ - **Key**: `'column'`
426
+ - **Config Type**: `NgsFormsFormItemContainerConfigBase`
427
+
428
+ ```typescript
429
+ import { NgsFormsColumnComponent } from '@ng-simplicity/forms-core';
430
+
431
+ const layoutCol = NgsFormsColumnComponent.create({
432
+ items: [
433
+ // Array of child NgsFormsFormItem<any> components
434
+ ]
435
+ });
436
+ ```
437
+
438
+ ### 8. HTML Content (`content-html`)
439
+ Inserts static HTML blocks directly into the layout.
440
+ - **Component**: `NgsFormsHtmlContentComponent`
441
+ - **Key**: `'content-html'`
442
+ - **Config Type**: `NgsFormItemHtmlContentConfig`
443
+
444
+ ```typescript
445
+ import { NgsFormsHtmlContentComponent } from '@ng-simplicity/forms-core';
446
+
447
+ const disclaimerText = NgsFormsHtmlContentComponent.create({
448
+ html: '<p class="text-muted">By clicking register, you agree to our policies.</p>',
449
+ sanitize: true // Whether to run sanitizer on the html
450
+ });
451
+ ```
452
+
453
+ ### 9. Text Div (`text-div`)
454
+ Inserts a basic text div.
455
+ - **Component**: `NgsFormsTextDivComponent`
456
+ - **Key**: `'text-div'`
457
+ - **Config Type**: `NgsFormsTextDivConfig`
458
+
459
+ ```typescript
460
+ import { NgsFormsTextDivComponent } from '@ng-simplicity/forms-core';
461
+
462
+ const headerText = NgsFormsTextDivComponent.create({
463
+ text: 'Billing Details',
464
+ classes: 'fw-bold mb-2' // CSS class name for styling
465
+ });
466
+ ```
467
+
468
+ ---
469
+
154
470
  ## Extension Reference Guide
155
471
 
156
472
  When writing custom controls, select the appropriate base class and configuration interface to inherit from.
@@ -167,8 +483,9 @@ All base classes are exported from `@ng-simplicity/forms-core`:
167
483
  ### Available Configuration Interfaces
168
484
  Ensure your custom configuration type extends one of these core interfaces for full TypeScript safety:
169
485
 
170
- * **`NgsFormsFormItemConfigBase`**: Standard base structure containing only an optional `uuid?: string`.
486
+ * **`NgsFormsFormItemConfigBase`**: Standard base structure containing optional `uuid?: string`, `visible?: boolean`, `visible$?: Observable<boolean>`, and `initialState?: any`.
171
487
  * **`NgsFormsFormItemConfigBaseItemWithNameAndValidators`**: Core interface for registerable form controls. Adds `name: string`, `errorMessageMap?: NgsFormsFormErrorKeyValueMap`, `disabled?`/`disabled$?`, and `validators?`/`validators$?`.
488
+ * **`NgsFormsFormGroupConfig`**: Extends both the name/validators base and the items container base. Used for FormGroup configurations.
172
489
  * **`NgsFormsFormItemConfigBaseInput`**: Extends the name/validators base. Adds common interactive input properties: `id?: string`, `label: string`, and `value?: unknown`.
173
490
  * **`NgsFormsFormItemConfigBaseTextInput`**: Extends the input base. Adds text-specific options: `placeholder?: string` and `type?: 'text' | 'email' | 'password'`.
174
491
  * **`NgsFormsFormItemConfigBaseInputWithOptions`**: Extends the input base. Adds selection fields: `options?: Array<NgsFormsFormInputOption>` and `options$?: Observable<Array<NgsFormsFormInputOption>>`.
@@ -186,3 +503,17 @@ Ensure your custom configuration type extends one of these core interfaces for f
186
503
  ```bash
187
504
  nx test forms-core --codeCoverage
188
505
  ```
506
+
507
+ ---
508
+
509
+ ## Example Applications
510
+
511
+ Fully functional example applications demonstrating the form engine functionality are located in the [`apps/`](../../apps) folder of this monorepo:
512
+ - **`forms-bootstrap-demo`**: Integrates and showcases the `@ng-simplicity/forms-bootstrap` package.
513
+ - **`forms-material-demo`**: Integrates and showcases the `@ng-simplicity/forms-material` package.
514
+
515
+ ---
516
+
517
+ ## Support & Contributions
518
+
519
+ If you have feature suggestions, need a feature to make `@ng-simplicity/forms-core` work for your project, or encounter any bugs, please log an issue in the GitHub issue tracker.