Skip to main content
The TanStackField directive provides a way to create and manage form fields in Angular applications using TanStack Form.

Import

import { TanStackField } from '@tanstack/angular-form'

Selector

[tanstackField]

Inputs

tanstackField
FormApi<TParentData>
required
The parent form API instance.
name
TName
required
The field name as a path string (e.g., ‘user.firstName’ or ‘items[0].name’).
defaultValue
TData
The default value for the field.
validators
FieldValidators<TParentData, TName, TData>
Validation functions for the field.
validators.onChange
FieldValidateOrFn<TParentData, TName, TData>
Validator that runs on every change.
validators.onChangeAsync
FieldAsyncValidateOrFn<TParentData, TName, TData>
Async validator that runs on change.
validators.onChangeAsyncDebounceMs
number
Debounce time in milliseconds for async validation.
validators.onBlur
FieldValidateOrFn<TParentData, TName, TData>
Validator that runs when the field loses focus.
validators.onMount
FieldValidateOrFn<TParentData, TName, TData>
Validator that runs when the field is mounted.
asyncDebounceMs
number
Debounce time in milliseconds for async validation. Default is 500ms.
asyncAlways
boolean
If true, async validation runs even if sync validation fails.
mode
'value' | 'array'
The field mode. Use ‘array’ for array fields to enable array-specific methods.
disableErrorFlat
boolean
If true, errors won’t be flattened to a single array.

Export As

#fieldName="field"
The directive can be exported with a template reference variable to access the field API.

Exported Properties

api
FieldApi<TParentData, TName, TData>
The field API instance.
api.name
TName
The field name.
api.state
FieldState<TData>
The current field state.
api.state.value
TData
The current field value.
api.state.meta
FieldMeta
Field metadata.
api.state.meta.errors
string[]
Current validation errors.
api.state.meta.isTouched
boolean
Whether the field has been touched.
api.state.meta.isDirty
boolean
Whether the field value has changed.
api.state.meta.isValidating
boolean
Whether async validation is in progress.
api.handleChange
(value: TData) => void
Update the field value.
api.handleBlur
() => void
Mark the field as touched.
api.pushValue
(value: TData extends Array<infer U> ? U : never) => void
Add a value to an array field (only available when mode=‘array’).
api.removeValue
(index: number) => void
Remove a value from an array field (only available when mode=‘array’).

Usage Example

Basic Field

import { Component } from '@angular/core'
import { injectForm, TanStackField } from '@tanstack/angular-form'
import type { FieldValidateFn } from '@tanstack/angular-form'

@Component({
  selector: 'app-form',
  standalone: true,
  imports: [TanStackField],
  template: `
    <form (submit)="handleSubmit($event)">
      <ng-container
        [tanstackField]="form"
        name="email"
        [validators]="{ onChange: emailValidator }"
        #email="field"
      >
        <label [for]="email.api.name">Email:</label>
        <input
          [id]="email.api.name"
          [name]="email.api.name"
          [value]="email.api.state.value"
          (blur)="email.api.handleBlur()"
          (input)="email.api.handleChange($any($event).target.value)"
        />
        @if (email.api.state.meta.isTouched && email.api.state.meta.errors.length > 0) {
          <div style="color: red">
            {{ email.api.state.meta.errors[0] }}
          </div>
        }
      </ng-container>
      <button type="submit">Submit</button>
    </form>
  `,
})
export class FormComponent {
  emailValidator: FieldValidateFn<any, string, any> = ({ value }) => {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
    return !emailRegex.test(value) ? 'Invalid email address' : undefined
  }

  form = injectForm({
    defaultValues: {
      email: '',
    },
    onSubmit({ value }) {
      console.log('Email:', value.email)
    },
  })

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

Array Field

import { Component } from '@angular/core'
import { injectForm, TanStackField } from '@tanstack/angular-form'

@Component({
  selector: 'app-array-form',
  standalone: true,
  imports: [TanStackField],
  template: `
    <form>
      <ng-container
        [tanstackField]="form"
        name="items"
        mode="array"
        #items="field"
      >
        @for (item of items.api.state.value; track $index) {
          <div>
            <ng-container
              [tanstackField]="form"
              [name]="'items[' + $index + '].name'"
              #itemField="field"
            >
              <input
                [value]="itemField.api.state.value"
                (input)="itemField.api.handleChange($any($event).target.value)"
              />
            </ng-container>
            <button (click)="items.api.removeValue($index)" type="button">
              Remove
            </button>
          </div>
        }
        <button (click)="items.api.pushValue({ name: '' })" type="button">
          Add Item
        </button>
      </ng-container>
    </form>
  `,
})
export class ArrayFormComponent {
  form = injectForm({
    defaultValues: {
      items: [] as Array<{ name: string }>,
    },
    onSubmit({ value }) {
      console.log(value)
    },
  })
}

Field with Async Validation

import { Component } from '@angular/core'
import { injectForm, TanStackField } from '@tanstack/angular-form'
import type { FieldValidateAsyncFn } from '@tanstack/angular-form'

@Component({
  selector: 'app-async-form',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="username"
      [validators]="{
        onChange: usernameValidator,
        onChangeAsync: usernameAsyncValidator,
        onChangeAsyncDebounceMs: 500,
      }"
      #username="field"
    >
      <input
        [value]="username.api.state.value"
        (input)="username.api.handleChange($any($event).target.value)"
      />
      @if (username.api.state.meta.isValidating) {
        <p>Validating...</p>
      }
      @if (username.api.state.meta.errors.length > 0) {
        <div style="color: red">
          {{ username.api.state.meta.errors[0] }}
        </div>
      }
    </ng-container>
  `,
})
export class AsyncFormComponent {
  usernameValidator = ({ value }: { value: string }) =>
    value.length < 3 ? 'Username must be at least 3 characters' : undefined

  usernameAsyncValidator: FieldValidateAsyncFn<any, string, any> = async ({
    value,
  }) => {
    await new Promise((resolve) => setTimeout(resolve, 1000))
    return value === 'taken' ? 'Username already taken' : undefined
  }

  form = injectForm({
    defaultValues: {
      username: '',
    },
    onSubmit({ value }) {
      console.log(value)
    },
  })
}

See Also

Build docs developers (and LLMs) love