Simplified API
The simplified array API provides a concise way to define dynamic arrays. Instead of manually specifying each item in fields, you provide a template for the item structure and a value array with initial data. Add and remove buttons are generated automatically.
When should I use this? For most array use cases -- primitive lists, object lists, and standard add/remove workflows -- the simplified API is the right choice. For advanced scenarios like heterogeneous items or custom button placement, see
Form Arrays (Complete) .
Interactive Demo
Overview
The simplified API uses two key properties:
| Property | Description |
|---|---|
template | Defines the structure of a single array item (single field or field array) |
value | Initial data array -- each element creates one pre-filled item |
minLength | Minimum number of array items (form invalid if fewer) |
maxLength | Maximum number of array items (form invalid if more) |
The template shape determines the array variant:
- Single field (
template: { ... }) -- primitive array (e.g.['tag1', 'tag2']) - Array of fields (
template: [{ ... }, { ... }]) -- object array (e.g.[{ name: 'Jane', phone: '555' }])
// Discriminant: template is a single field → primitive array
{ key: 'tags', type: 'array', template: { key: 'value', type: 'input', label: 'Tag' } }
// Discriminant: template is an array of fields → object array
{ key: 'contacts', type: 'array', template: [
{ key: 'name', type: 'input', label: 'Name' },
{ key: 'phone', type: 'input', label: 'Phone' },
] }Primitive Arrays
For simple arrays of scalar values like ['angular', 'typescript'], provide a single field as the template and a flat array as the value:
{
key: 'tags',
type: 'array',
template: { key: 'value', type: 'input', label: 'Tag', required: true },
value: ['angular', 'typescript'],
}This renders two input fields pre-filled with 'angular' and 'typescript', each with an auto-generated Remove button. An Add button is placed after the array.
Form value output:
{
tags: ['angular', 'typescript'];
}Object Arrays
For arrays of objects like [{ name: 'Jane', phone: '555' }], provide an array of fields as the template and an array of objects as the value:
{
key: 'contacts',
type: 'array',
template: [
{ key: 'name', type: 'input', label: 'Contact Name', required: true },
{ key: 'phone', type: 'input', label: 'Phone Number' },
],
value: [
{ name: 'Jane', phone: '555-1234' },
{ name: 'Bob', phone: '555-5678' },
],
}Each value object is matched to the template by key. If a value object omits a key, the corresponding field renders without an initial value.
Form value output:
{
contacts: [
{ name: 'Jane', phone: '555-1234' },
{ name: 'Bob', phone: '555-5678' },
];
}Empty Arrays
Omit value or set value: [] to start with an empty array. Users populate it via the auto-generated Add button:
// No value property — starts empty
{
key: 'tags',
type: 'array',
template: { key: 'value', type: 'input', label: 'Tag' },
}
// Explicit empty array — same behavior
{
key: 'emails',
type: 'array',
template: [
{ key: 'address', type: 'input', label: 'Email Address', required: true },
{ key: 'label', type: 'select', label: 'Type', options: [
{ label: 'Work', value: 'work' },
{ label: 'Personal', value: 'personal' },
] },
],
value: [],
}Button Customization
By default, the library generates an Add button (label: "Add") after the array and a Remove button (label: "Remove") inside each item. You can customize both via addButton and removeButton:
{
key: 'tags',
type: 'array',
template: { key: 'value', type: 'input', label: 'Tag' },
value: ['angular'],
addButton: { label: 'Add Tag', props: { color: 'primary' } },
removeButton: { label: 'Delete', props: { color: 'warn' } },
}Button Config Properties
| Property | Type | Description |
|---|---|---|
label | string | Custom label text for the button |
props | Record<string, unknown> | Additional properties passed to the button component |
Button Opt-Out
Set addButton: false or removeButton: false to suppress the corresponding auto-generated button entirely:
// No add button — items are only removable, not addable
{
key: 'defaultTags',
type: 'array',
template: { key: 'value', type: 'input', label: 'Tag' },
value: ['featured', 'popular'],
addButton: false,
}
// No remove button — items are only addable, not removable
{
key: 'skills',
type: 'array',
template: { key: 'value', type: 'input', label: 'Skill' },
value: ['TypeScript'],
removeButton: false,
}
// No buttons at all — static list rendered from initial values
{
key: 'readonlyItems',
type: 'array',
template: { key: 'value', type: 'input', label: 'Item' },
value: ['A', 'B', 'C'],
addButton: false,
removeButton: false,
}When removeButton: false is set for a primitive array, each item renders as a plain field without being wrapped in a row. When set for an object array, the remove button is simply omitted from the item's field list.
Complete Example: Primitive Array
A full working configuration for a tag list with custom buttons:
import { FormConfig } from '@ng-forge/dynamic-forms';
const formConfig : FormConfig = {
fields: [
{
key: 'projectName',
type: 'input',
label: 'Project Name',
required: true,
},
{
key: 'tags',
type: 'array',
template: { key: 'value', type: 'input', label: 'Tag', required: true, minLength: 2 },
value: ['angular', 'open-source'],
addButton: { label: 'Add Tag' },
removeButton: { label: 'Remove Tag' },
},
],
};
// Form value:
// {
// projectName: 'My Project',
// tags: ['angular', 'open-source']
// }Complete Example: Object Array
A full working configuration for a contact list:
import { FormConfig } from '@ng-forge/dynamic-forms';
const formConfig : FormConfig = {
fields: [
{
key: 'teamName',
type: 'input',
label: 'Team Name',
required: true,
},
{
key: 'members',
type: 'array',
template: [
{ key: 'name', type: 'input', label: 'Full Name', required: true },
{ key: 'email', type: 'input', label: 'Email', required: true },
{
key: 'role',
type: 'select',
label: 'Role',
options: [
{ label: 'Developer', value: 'dev' },
{ label: 'Designer', value: 'design' },
{ label: 'Manager', value: 'manager' },
],
},
],
value: [
{ name: 'Alice', email: 'alice@example.com', role: 'dev' },
{ name: 'Bob', email: 'bob@example.com', role: 'design' },
],
addButton: { label: 'Add Member' },
removeButton: { label: 'Remove' },
},
],
};
// Form value:
// {
// teamName: 'Frontend Team',
// members: [
// { name: 'Alice', email: 'alice@example.com', role: 'dev' },
// { name: 'Bob', email: 'bob@example.com', role: 'design' },
// ]
// }Form Value Output
The simplified API produces the same form values as the complete API:
| Template Shape | Value Shape | Output |
|---|---|---|
| Single field | Flat array of scalars | { tags: ['angular', 'typescript'] } |
| Array of fields | Array of objects | { contacts: [{ name: 'Jane', phone: '555' }] } |
| Any template | [] or omitted | { items: [] } |
Array Size Validation
Use minLength and maxLength to constrain the number of items. When violated, the form becomes invalid (e.g. submit button is disabled).
{
key: 'tags',
type: 'array',
template: { key: 'value', type: 'input', label: 'Tag' },
value: ['angular'],
minLength: 1,
maxLength: 5,
}Both properties are optional and can be used independently or together.
Conditional Visibility
Simplified arrays support logic for conditional visibility, just like the complete API:
{
key: 'extras',
type: 'array',
template: { key: 'value', type: 'input', label: 'Extra' },
value: [],
logic: [{ type: 'hidden', condition: { type: 'fieldValue', fieldPath: 'showExtras', operator: 'equals', value: false } }],
}Decision Guide: Simplified vs Complete API
| Scenario | Recommended API |
|---|---|
| Uniform items with standard add/remove | Simplified |
| Pre-filled values from a data source | Simplified |
| Custom button labels or styling | Simplified |
| Empty array populated by users | Simplified |
| Static list (no add/remove buttons) | Simplified |
| Heterogeneous items (different fields per item) | Complete |
| Custom button placement (buttons outside array) | Complete |
| Programmatic control via EventBus | Complete |
| Multiple add buttons (append, prepend, insert) | Complete |
| Nested arrays inside item templates | Complete |
As a rule of thumb: start with the simplified API. If you hit a limitation, switch to the