mirror of https://github.com/abpframework/abp.git
csharpabpc-sharpframeworkblazoraspnet-coredotnet-coreaspnetcorearchitecturesaasdomain-driven-designangularmulti-tenancy
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6.7 KiB
6.7 KiB
Nested Forms Guide
Overview
Dynamic Form now supports nested forms with two new field types:
group- Group related fields togetherarray- Dynamic lists with add/remove functionality
Quick Start
1. Group Type (Nested Fields)
Group 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"
}
}
2. Array Type (Dynamic Lists)
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" }
]
}
Features
Array Features
- ✅ Add Button - Adds new item (respects maxItems)
- ✅ Remove Button - Removes item (respects minItems)
- ✅ Item Counter - Shows current count and limits
- ✅ Item Labels - "Phone Number #1", "Phone Number #2"
- ✅ Min/Max Validation - Buttons automatically disabled
- ✅ Empty State - Shows info message when no items
Group Features
- ✅ Visual Hierarchy - Border and background styling
- ✅ Legend Label - Fieldset with legend for accessibility
- ✅ Grid Support - All children support gridSize
- ✅ Nested Groups - Groups inside groups supported
Recursive Support
- ✅ Array in Array - Phone numbers can have sub-arrays
- ✅ Group in Array - Work experience can have grouped fields
- ✅ Array in Group - Address can have multiple phone numbers
- ✅ Unlimited Nesting - No depth limit
Advanced Examples
Complex Nested Structure
{
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
}
]
}
API Reference
FormFieldConfig (Extended)
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)
}
New Components
DynamicFormGroupComponent
@Input() groupConfig: FormFieldConfig;
@Input() formGroup: FormGroup;
@Input() visible: boolean = true;
DynamicFormArrayComponent
@Input() arrayConfig: FormFieldConfig;
@Input() formGroup: FormGroup;
@Input() visible: boolean = true;
addItem(): void; // Add new item to array
removeItem(index): void; // Remove item from array
Styling
Group Styling
.form-group-container {
border-left: 3px solid var(--bs-primary);
padding: 1rem;
background-color: var(--bs-light);
}
Array Styling
.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);
}
}
Accessibility
All nested forms include:
- ✅ ARIA roles (
role="group",role="list",role="listitem") - ✅ ARIA labels (
aria-label,aria-labelledby) - ✅ Live regions (
aria-live="polite"for item count) - ✅ Semantic HTML (
<fieldset>,<legend>) - ✅ Keyboard navigation (Tab, Enter, Space)
- ✅ Screen reader announcements
Migration Guide
From Simple to Nested
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' }
]
}
Data Structure Change
Before:
{
"street": "123 Main St",
"city": "New York"
}
After:
{
"address": {
"street": "123 Main St",
"city": "New York"
}
}
Best Practices
- Use Groups for logical field grouping (address, contact info)
- Use Arrays for dynamic lists (phone numbers, work history)
- Set minItems/maxItems to prevent empty or excessive arrays
- Use gridSize for responsive layouts within nested forms
- Keep nesting shallow (max 2-3 levels for UX)
- Add validation to required nested fields
- Use meaningful labels for array items
Examples
See apps/dev-app/src/app/dynamic-form-page for complete examples:
- Phone Numbers (simple array)
- Work Experience (complex array)
- Address (group)
Troubleshooting
Array items not showing
- Check
minItems- may need to be > 0 - Verify
childrenarray is not empty
Can't add items
- Check
maxItemslimit - Verify button is not disabled
Form data not nested
- Confirm
type: 'group'ortype: 'array' - Check FormGroup structure in component
Performance
- ✅ OnPush change detection
- ✅ TrackBy functions for arrays
- ✅ Lazy rendering for conditional fields
- ✅ Minimal re-renders on add/remove
Version: 1.0.0
Added: 2026-01-20
Status: Production Ready ✅