npm/ng-packs/packages/components/dynamic-form/NESTED-FORMS.md
Dynamic Form now supports nested forms with two new field types:
group - Group related fields togetherarray - Dynamic lists with add/remove functionalityGroup related fields together with visual hierarchy:
{
key: 'address',
type: 'group',
label: 'Address Information',
gridSize: 12,
children: [
{
key: 'street',
type: 'text',
label: 'Street',
gridSize: 8
},
{
key: 'city',
type: 'text',
label: 'City',
gridSize: 4
},
{
key: 'zipCode',
type: 'text',
label: 'ZIP Code',
gridSize: 6
}
]
}
Output:
{
"address": {
"street": "123 Main St",
"city": "New York",
"zipCode": "10001"
}
}
Create dynamic lists with add/remove buttons:
{
key: 'phoneNumbers',
type: 'array',
label: 'Phone Numbers',
minItems: 1,
maxItems: 5,
gridSize: 12,
children: [
{
key: 'type',
type: 'select',
label: 'Type',
gridSize: 4,
options: {
defaultValues: [
{ key: 'mobile', value: 'Mobile' },
{ key: 'home', value: 'Home' },
{ key: 'work', value: 'Work' }
]
}
},
{
key: 'number',
type: 'tel',
label: 'Number',
gridSize: 8
}
]
}
Output:
{
"phoneNumbers": [
{ "type": "mobile", "number": "555-1234" },
{ "type": "work", "number": "555-5678" }
]
}
{
key: 'workExperience',
type: 'array',
label: 'Work Experience',
minItems: 0,
maxItems: 10,
children: [
{
key: 'company',
type: 'text',
label: 'Company Name',
gridSize: 6,
required: true
},
{
key: 'position',
type: 'text',
label: 'Position',
gridSize: 6,
required: true
},
{
key: 'dates',
type: 'group', // Nested group inside array
label: 'Employment Dates',
gridSize: 12,
children: [
{
key: 'startDate',
type: 'date',
label: 'Start Date',
gridSize: 6
},
{
key: 'endDate',
type: 'date',
label: 'End Date',
gridSize: 6
}
]
},
{
key: 'description',
type: 'textarea',
label: 'Description',
gridSize: 12
}
]
}
interface FormFieldConfig {
// ... existing properties
// NEW: Nested form properties
children?: FormFieldConfig[]; // Child fields for group/array types
minItems?: number; // Minimum items for array (default: 0)
maxItems?: number; // Maximum items for array (default: unlimited)
}
@Input() groupConfig: FormFieldConfig;
@Input() formGroup: FormGroup;
@Input() visible: boolean = true;
@Input() arrayConfig: FormFieldConfig;
@Input() formGroup: FormGroup;
@Input() visible: boolean = true;
addItem(): void; // Add new item to array
removeItem(index): void; // Remove item from array
.form-group-container {
border-left: 3px solid var(--bs-primary);
padding: 1rem;
background-color: var(--bs-light);
}
.array-item {
border: 1px solid var(--bs-border-color);
padding: 1rem;
margin-bottom: 1rem;
background: white;
&:hover {
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
}
All nested forms include:
role="group", role="list", role="listitem")aria-label, aria-labelledby)aria-live="polite" for item count)<fieldset>, <legend>)Before:
{
key: 'street',
type: 'text',
label: 'Street'
},
{
key: 'city',
type: 'text',
label: 'City'
}
After:
{
key: 'address',
type: 'group',
label: 'Address',
children: [
{ key: 'street', type: 'text', label: 'Street' },
{ key: 'city', type: 'text', label: 'City' }
]
}
Before:
{
"street": "123 Main St",
"city": "New York"
}
After:
{
"address": {
"street": "123 Main St",
"city": "New York"
}
}
See apps/dev-app/src/app/dynamic-form-page for complete examples:
minItems - may need to be > 0children array is not emptymaxItems limittype: 'group' or type: 'array'