Form Arrays
Arrays create dynamic collections of field values. The fields array defines a template that is cloned for each array item.
Interactive Demo
Flat Arrays (Primitive Values)
For simple arrays of primitive values, use a leaf field as the template:
{
key: 'tags',
type: 'array',
fields: [
{ key: 'tag', type: 'input', label: 'Tag', value: '' }
],
}This creates a flat array in the form value:
{
tags: ['tag1', 'tag2', 'tag3'];
}Object Arrays (Nested Groups)
For arrays of objects with nested structure, use a group field as the template:
{
key: 'contacts',
type: 'array',
fields: [{
key: 'contact',
type: 'group',
fields: [
{ key: 'name', type: 'input', label: 'Name', value: '' },
{ key: 'phone', type: 'input', label: 'Phone', value: '' }
]
}],
}This creates an array of nested objects (note the group key creates the nesting):
{
contacts: [{ contact: { name: 'John Doe', phone: '5551234567' } }, { contact: { name: 'Jane Smith', phone: '5559876543' } }];
}For flat object arrays without nesting, use a row as the template instead (rows don't add nesting).
Array vs Group
- Groups create nested objects with keys:
{ address: { street: '', city: '' } } - Flat Arrays create lists of values:
{ tags: ['value1', 'value2'] } - Object Arrays create lists of objects:
{ items: [{name: ''}, {name: ''}] }
Dynamic Add/Remove
Array items can be added or removed dynamically at runtime using the event bus:
import { EventBus , AddArrayItemEvent , RemoveArrayItemEvent } from '@ng-forge/dynamic-forms';
// Inject the event bus
eventBus = inject(EventBus );
// Add item to array
addItem() {
this.eventBus.dispatch(AddArrayItemEvent , 'tags');
}
// Add item at specific index
addItemAt(index: number) {
this.eventBus.dispatch(AddArrayItemEvent , 'tags', index);
}
// Remove last item
removeItem() {
this.eventBus.dispatch(RemoveArrayItemEvent , 'tags');
}
// Remove item at specific index
removeItemAt(index: number) {
this.eventBus.dispatch(RemoveArrayItemEvent , 'tags', index);
}Use Cases
Arrays are ideal for:
- Lists of simple values (tags, categories, keywords)
- Repeating form sections (multiple addresses, phone numbers)
- Dynamic collections where items can be added/removed
- Collection-based data structures where order matters
Complete Example: Flat Array
Here's a complete working example of a flat array field with dynamic add/remove:
import { Component, inject } from '@angular/core';
import { DynamicForm , EventBus , AddArrayItemEvent , RemoveArrayItemEvent } from '@ng-forge/dynamic-forms';
@Component({
selector: 'app-tags-form',
imports: [DynamicForm ],
template: `
<form [dynamic-form]="formConfig "></form>
<button (click)="addTag()">Add Tag</button>
`,
})
export class TagsFormComponent {
private eventBus = inject(EventBus );
formConfig = {
fields: [
// Note: Array fields don't support the 'label' property.
// Use a text field above if you need a section header.
{
key: 'tags',
type: 'array',
fields: [
{
key: 'tag',
type: 'input',
label: 'Tag',
value: '',
required: true,
minLength: 2,
},
],
},
],
};
addTag() {
this.eventBus.dispatch(AddArrayItemEvent , 'tags');
}
removeTag(index: number) {
this.eventBus.dispatch(RemoveArrayItemEvent , 'tags', index);
}
}Complete Example: Object Array
Here's a complete working example of an object array field with validation:
import { Component, inject } from '@angular/core';
import { DynamicForm , EventBus , AddArrayItemEvent , RemoveArrayItemEvent } from '@ng-forge/dynamic-forms';
@Component({
selector: 'app-contacts-form',
imports: [DynamicForm ],
template: `
<form [dynamic-form]="formConfig "></form>
<button (click)="addContact()">Add Contact</button>
`,
})
export class ContactsFormComponent {
private eventBus = inject(EventBus );
formConfig = {
fields: [
// Note: Array fields don't support the 'label' property.
// Use a text field above if you need a section header.
{
key: 'contacts',
type: 'array',
fields: [
{
key: 'contact',
type: 'group',
fields: [
{
key: 'name',
type: 'input',
label: 'Contact Name',
value: '',
required: true,
minLength: 2,
},
{
key: 'phone',
type: 'input',
label: 'Phone Number',
value: '',
required: true,
pattern: /^d{10}$/,
},
{
key: 'relationship',
type: 'select',
label: 'Relationship',
value: 'friend',
options: [
{ label: 'Family', value: 'family' },
{ label: 'Friend', value: 'friend' },
{ label: 'Colleague', value: 'colleague' },
],
},
],
},
],
},
],
};
addContact() {
this.eventBus.dispatch(AddArrayItemEvent , 'contacts');
}
removeContact(index: number) {
this.eventBus.dispatch(RemoveArrayItemEvent , 'contacts', index);
}
}Template Field Concept
The key concept is that the fields array contains a template (typically one field definition):
- Flat arrays: Template is a leaf field → creates
[value1, value2] - Object arrays: Template is a group field with key → creates
[{groupKey: {...}}, ...] - Flat object arrays: Template is a row field → creates
[{...}, {...}](no nesting)
When you add an array item via the event bus, the template is cloned and a new instance is created with the appropriate default value.
Nesting Constraints
Array fields can be used within:
- Pages (top-level container)
- Rows (for horizontal layouts)
- Groups (for nested arrays within objects)
Arrays cannot contain:
- Other array fields (no nested arrays)
- Page fields
Allowed Children (as template)
Arrays can use these field types as templates:
- Leaf fields (input, select, checkbox, etc.) → creates flat arrays
- Group fields with key → creates nested object arrays
[{groupKey: {...}}, ...] - Row fields → creates flat object arrays
[{...}, {...}](rows don't add nesting)
See