Skip to main content
TanStack Form supports array fields for managing dynamic lists of values, including arrays of objects with nested fields.

Basic Usage

Create an array field using the tanstackField directive and iterate over the values:
import { Component } from '@angular/core'
import { TanStackField, injectForm } from '@tanstack/angular-form'

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container [tanstackField]="form" name="people" #people="field">
      <div>
        @for (_ of people.api.state.value; track $index) {
          <!-- Render each item -->
        }
      </div>
    </ng-container>
  `,
})
export class AppComponent {
  form = injectForm({
    defaultValues: {
      people: [] as Array<{ name: string; age: number }>,
    },
    onSubmit({ value }) {
      alert(JSON.stringify(value))
    },
  })
}

Adding Items

Use the pushValue method to add items to the array:
<button (click)="people.api.pushValue(defaultPerson)" type="button">
  Add person
</button>
Define the default value in your component:
export class AppComponent {
  defaultPerson = { name: '', age: 0 }

  // ...
}

Accessing Nested Fields

Access nested fields using bracket notation and a helper function:
<ng-container
  [tanstackField]="form"
  [name]="getPeopleName($index)"
  #person="field"
>
  <div>
    <label>
      <div>Name for person {{ $index }}</div>
      <input
        [value]="person.api.state.value"
        (input)="person.api.handleChange($any($event).target.value)"
      />
    </label>
  </div>
</ng-container>
Define the helper function with proper typing:
export class AppComponent {
  getPeopleName = (idx: number) => `people[${idx}].name` as const

  // ...
}
The helper function is required for TypeScript type safety. Template string interpolation like "people[" + $index + "].name" produces a string type instead of the required literal type.

Complete Example

Here’s a full example with array management:
import { Component } from '@angular/core'
import { TanStackField, injectForm, injectStore } from '@tanstack/angular-form'

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <form (submit)="handleSubmit($event)">
      <div>
        <ng-container [tanstackField]="form" name="people" #people="field">
          <div>
            @for (_ of people.api.state.value; track $index) {
              <ng-container
                [tanstackField]="form"
                [name]="getPeopleName($index)"
                #person="field"
              >
                <div>
                  <label>
                    <div>Name for person {{ $index }}</div>
                    <input
                      [value]="person.api.state.value"
                      (input)="person.api.handleChange($any($event).target.value)"
                    />
                  </label>
                </div>
              </ng-container>
            }
          </div>
          <button (click)="people.api.pushValue(defaultPerson)" type="button">
            Add person
          </button>
        </ng-container>
      </div>
      <button type="submit" [disabled]="!canSubmit()">
        {{ isSubmitting() ? '...' : 'Submit' }}
      </button>
    </form>
  `,
})
export class AppComponent {
  defaultPerson = { name: '', age: 0 }

  form = injectForm({
    defaultValues: {
      people: [] as Array<{ name: string; age: number }>,
    },
    onSubmit({ value }) {
      alert(JSON.stringify(value))
    },
  })

  getPeopleName = (idx: number) => `people[${idx}].name` as const

  canSubmit = injectStore(this.form, (state) => state.canSubmit)
  isSubmitting = injectStore(this.form, (state) => state.isSubmitting)

  handleSubmit(event: SubmitEvent) {
    event.preventDefault()
    event.stopPropagation()
    this.form.handleSubmit()
  }
}

Array Field Methods

The field API provides several methods for managing array values:

pushValue

Add a value to the end of the array:
people.api.pushValue({ name: 'John', age: 30 })

removeValue

Remove a value at a specific index:
people.api.removeValue(index)
Example with a remove button:
<button
  type="button"
  (click)="people.api.removeValue($index)"
>
  Remove
</button>

insertValue

Insert a value at a specific index:
people.api.insertValue(2, { name: 'Jane', age: 25 })

replaceValue

Replace a value at a specific index:
people.api.replaceValue(1, { name: 'Bob', age: 35 })

swapValues

Swap two values by their indices:
people.api.swapValues(0, 2)

moveValue

Move a value from one index to another:
people.api.moveValue(0, 3)

clearValues

Remove all values from the array:
people.api.clearValues()

Nested Object Fields

Work with multiple nested fields within array items:
import { Component } from '@angular/core'
import { TanStackField, injectForm } from '@tanstack/angular-form'

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container [tanstackField]="form" name="hobbies" #hobbies="field">
      <div>
        Hobbies
        <div>
          @if (!hobbies.api.state.value.length) {
            No hobbies found
          }
          @for (_ of hobbies.api.state.value; track $index) {
            <div>
              <ng-container
                [tanstackField]="form"
                [name]="getHobbyName($index)"
                #hobbyName="field"
              >
                <div>
                  <label [for]="hobbyName.api.name">Name:</label>
                  <input
                    [id]="hobbyName.api.name"
                    [name]="hobbyName.api.name"
                    [value]="hobbyName.api.state.value"
                    (blur)="hobbyName.api.handleBlur()"
                    (input)="hobbyName.api.handleChange($any($event).target.value)"
                  />
                  <button
                    type="button"
                    (click)="hobbies.api.removeValue($index)"
                  >
                    X
                  </button>
                </div>
              </ng-container>
              <ng-container
                [tanstackField]="form"
                [name]="getHobbyDesc($index)"
                #hobbyDesc="field"
              >
                <div>
                  <label [for]="hobbyDesc.api.name">Description:</label>
                  <input
                    [id]="hobbyDesc.api.name"
                    [name]="hobbyDesc.api.name"
                    [value]="hobbyDesc.api.state.value"
                    (blur)="hobbyDesc.api.handleBlur()"
                    (input)="hobbyDesc.api.handleChange($any($event).target.value)"
                  />
                </div>
              </ng-container>
            </div>
          }
        </div>
        <button type="button" (click)="hobbies.api.pushValue(defaultHobby)">
          Add hobby
        </button>
      </div>
    </ng-container>
  `,
})
export class AppComponent {
  defaultHobby = {
    name: '',
    description: '',
    yearsOfExperience: 0,
  }

  getHobbyName = (idx: number) => `hobbies[${idx}].name` as const
  getHobbyDesc = (idx: number) => `hobbies[${idx}].description` as const

  form = injectForm({
    defaultValues: {
      hobbies: [] as Array<{
        name: string
        description: string
        yearsOfExperience: number
      }>,
    },
    onSubmit({ value }) {
      alert(JSON.stringify(value))
    },
  })
}

Array Validation

Validate array fields just like regular fields:
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="people"
      [validators]="{
        onChange: peopleValidator
      }"
      #people="field"
    >
      <div>
        @for (_ of people.api.state.value; track $index) {
          <!-- ... -->
        }
      </div>
      @if (people.api.state.meta.errors) {
        <em role="alert">{{ people.api.state.meta.errors.join(', ') }}</em>
      }
    </ng-container>
  `,
})
export class AppComponent {
  peopleValidator: FieldValidateFn<any, any, any, any, any[]> = ({ value }) =>
    value.length === 0 ? 'At least one person is required' : undefined

  // ...
}

Validate Individual Array Items

Apply validation to specific fields within array items:
<ng-container
  [tanstackField]="form"
  [name]="getPeopleName($index)"
  [validators]="{
    onChange: nameValidator
  }"
  #person="field"
>
  <input
    [value]="person.api.state.value"
    (input)="person.api.handleChange($any($event).target.value)"
  />
  @if (person.api.state.meta.errors) {
    <em role="alert">{{ person.api.state.meta.errors.join(', ') }}</em>
  }
</ng-container>
nameValidator: FieldValidateFn<any, any, any, any, string> = ({ value }) =>
  value.length < 2 ? 'Name must be at least 2 characters' : undefined

Build docs developers (and LLMs) love