Prebuilt Components Form Arrays

Form Arrays

Arrays create dynamic collections of field values. The fields array defines a template that is cloned for each array item.

Interactive Demo

Loading example...

Click to view config! 🔧
{
  fields: [
    // Flat array: stores ['tag1', 'tag2']
    { key: 'tags', type: 'array', label: 'Tags',
      fields: [{ key: 'tag', type: 'row',
        fields: [
          { key: 'value', type: 'input', label: 'Tag', value: '', required: true, minLength: 2 },
          { key: 'removeTag', type: 'button', label: 'Remove', event: RemoveArrayItemEvent, eventArgs: ['$arrayKey', '$index'] }
        ]
      }]
    },
    { key: 'addTag', type: 'button', label: 'Add Tag', event: AddTagsEvent },

    // Object array: stores [{name: '', phone: '', relationship: ''}]
    { key: 'contacts', type: 'array', label: 'Emergency Contacts',
      fields: [{ key: 'contact', type: 'group',
        fields: [
          { key: 'name', type: 'input', label: 'Contact Name', required: true },
          { key: 'phone', type: 'input', label: 'Phone Number', required: true, pattern: /^\d{10}$/ },
          { key: 'relationship', type: 'select', label: 'Relationship',
            options: [{ label: 'Family', value: 'family' }, { label: 'Friend', value: 'friend' }] },
          { key: 'removeContact', type: 'button', label: 'Remove', event: RemoveArrayItemEvent, eventArgs: ['$arrayKey', '$index'] }
        ]
      }]
    },
    { key: 'addContact', type: 'button', label: 'Add Contact', event: AddContactEvent },
  ],
}

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, use a group field as the template:

{
  key: 'contacts',
  type: 'array',
  fields: [{
    type: 'group',
    fields: [
      { key: 'name', type: 'input', label: 'Name', value: '' },
      { key: 'phone', type: 'input', label: 'Phone', value: '' }
    ]
  }],
}

This creates an array of objects:

{
  contacts: [
    { name: 'John Doe', phone: '5551234567' },
    { name: 'Jane Smith', phone: '5559876543' },
  ];
}

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: [
          {
            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 → creates [{...}, {...}]

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 → creates object arrays
  • Row fields → for horizontal layouts within each array item

See Type Safety & Inference for details on how arrays affect type inference.