Grundlegende Konzepte und Terminologie

Diese Seite führt die grundlegenden Konzepte und Terminologie ein, die in der @tanstack/angular-form Bibliothek verwendet werden. Die Vertrautheit mit diesen Konzepten wird Ihnen helfen, die Bibliothek besser zu verstehen und mit ihr zu arbeiten.

Formularinstanz

Eine Formularinstanz (Form Instance) ist ein Objekt, das ein einzelnes Formular darstellt und Methoden und Eigenschaften für die Arbeit mit dem Formular bereitstellt. Sie erstellen eine Formularinstanz mit der Funktion injectForm. Der Hook akzeptiert ein Objekt mit einer onSubmit Funktion, die aufgerufen wird, wenn das Formular übermittelt wird.

typescript
const form = injectForm({
  onSubmit: async ({ value }) => {
    // Do something with form data
    console.log(value)
  },
})
const form = injectForm({
  onSubmit: async ({ value }) => {
    // Do something with form data
    console.log(value)
  },
})

Field

Ein Feld (Field) repräsentiert ein einzelnes Formulareingabeelement, wie z. B. eine Texteingabe oder eine Checkbox. Felder werden mit der Direktive tanstackField erstellt. Die Direktive akzeptiert eine `name`-Prop, die einem Schlüssel in den Standardwerten des Formulars entsprechen sollte. Sie stellt auch eine Instanz namens field der Interna der Direktive zur Verfügung, die über eine Vorlagenvariable verwendet werden sollte, um auf die Interna des Feldes zuzugreifen.

Beispiel

html
<ng-container [tanstackField]="form" name="firstName" #firstName="field">
  <input
    [value]="firstName.api.state.value"
    (blur)="firstName.api.handleBlur()"
    (input)="firstName.api.handleChange($any($event).target.value)"
  />
</ng-container>
<ng-container [tanstackField]="form" name="firstName" #firstName="field">
  <input
    [value]="firstName.api.state.value"
    (blur)="firstName.api.handleBlur()"
    (input)="firstName.api.handleChange($any($event).target.value)"
  />
</ng-container>

Feldstatus

Jedes Feld hat seinen eigenen Zustand (state), der seinen aktuellen Wert, den Validierungsstatus, Fehlermeldungen und andere Metadaten umfasst. Sie können auf den Zustand eines Feldes über die Eigenschaft fieldApi.state zugreifen.

Beispiel

ts
const {
  value,
  meta: { errors, isValidating },
} = field.state
const {
  value,
  meta: { errors, isValidating },
} = field.state

Es gibt vier Zustände in den Metadaten, die nützlich sein können, um zu sehen, wie der Benutzer mit einem Feld interagiert.

  • "isTouched", nachdem der Benutzer das Feld geändert oder den Fokus darauf verloren hat.
  • "isDirty", nachdem der Wert des Feldes geändert wurde, selbst wenn er auf den Standardwert zurückgesetzt wurde. Das Gegenteil von "isPristine".
  • "isPristine", bis der Benutzer den Feldwert ändert. Das Gegenteil von "isDirty".
  • "isBlurred", nachdem der Fokus vom Feld genommen wurde.
ts
const { isTouched, isDirty, isPristine, isBlurred } = field.state.meta
const { isTouched, isDirty, isPristine, isBlurred } = field.state.meta

Field states

Verständnis von 'isDirty' in verschiedenen Bibliotheken

Nicht-persistenter dirty Zustand

  • Bibliotheken: React Hook Form (RHF), Formik, Final Form.
  • Verhalten: Ein Feld ist 'dirty' (geändert), wenn sein Wert vom Standard abweicht. Das Zurücksetzen auf den Standardwert macht es wieder 'clean' (unverändert).

Persistenter dirty Zustand

  • Bibliotheken: Angular Form, Vue FormKit.
  • Verhalten: Ein Feld bleibt 'dirty' (geändert), sobald es geändert wurde, auch wenn es auf den Standardwert zurückgesetzt wird.

Wir haben uns für das persistente 'dirty'-Zustandsmodell entschieden. Um auch einen nicht-persistenten 'dirty'-Zustand zu unterstützen, führen wir ein zusätzliches Flag ein.

  • "isDefaultValue", ob der aktuelle Wert des Feldes der Standardwert ist.
ts
const { isDefaultValue, isTouched } = field.state.meta

// The following line will re-create the non-Persistent `dirty` functionality.
const nonPersistentIsDirty = !isDefaultValue
const { isDefaultValue, isTouched } = field.state.meta

// The following line will re-create the non-Persistent `dirty` functionality.
const nonPersistentIsDirty = !isDefaultValue

Field states extended

Feld-API

Die Feld-API (Field API) ist ein Objekt, auf das über die Eigenschaft tanstackField.api beim Erstellen eines Feldes zugegriffen wird. Sie bietet Methoden für die Arbeit mit dem Zustand des Feldes.

Beispiel

angular-html
<input
  [value]="fieldName.api.state.value"
  (blur)="fieldName.api.handleBlur()"
  (input)="fieldName.api.handleChange($any($event).target.value)"
/>
<input
  [value]="fieldName.api.state.value"
  (blur)="fieldName.api.handleBlur()"
  (input)="fieldName.api.handleChange($any($event).target.value)"
/>

Validierung

@tanstack/angular-form bietet sowohl synchrone als auch asynchrone Validierung "out of the box". Validierungsfunktionen können über die Prop validators an die Direktive tanstackField übergeben werden.

Beispiel

angular-ts
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container [tanstackField]="form" name="firstName" #firstName="field">
      <input
        [value]="firstName.api.state.value"
        (blur)="firstName.api.handleBlur()"
        (input)="firstName.api.handleChange($any($event).target.value)"
      />
    </ng-container>
  `,
})
export class AppComponent {
  firstNameValidator: FieldValidateFn<any, any, string, any> = ({
                                                                       value,
                                                                     }) =>
    !value
      ? 'A first name is required'
      : value.length < 3
        ? 'First name must be at least 3 characters'
        : undefined

  firstNameAsyncValidator: FieldValidateAsyncFn<any, string, any> =
    async ({ value }) => {
      await new Promise((resolve) => setTimeout(resolve, 1000))
      return value.includes('error') && 'No "error" allowed in first name'
    }

  form = injectForm({
    defaultValues: {
      firstName: '',
    },
    onSubmit({ value }) {
      console.log(value)
    },
  })
}
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container [tanstackField]="form" name="firstName" #firstName="field">
      <input
        [value]="firstName.api.state.value"
        (blur)="firstName.api.handleBlur()"
        (input)="firstName.api.handleChange($any($event).target.value)"
      />
    </ng-container>
  `,
})
export class AppComponent {
  firstNameValidator: FieldValidateFn<any, any, string, any> = ({
                                                                       value,
                                                                     }) =>
    !value
      ? 'A first name is required'
      : value.length < 3
        ? 'First name must be at least 3 characters'
        : undefined

  firstNameAsyncValidator: FieldValidateAsyncFn<any, string, any> =
    async ({ value }) => {
      await new Promise((resolve) => setTimeout(resolve, 1000))
      return value.includes('error') && 'No "error" allowed in first name'
    }

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

Validierung mit Standard-Schema-Bibliotheken

Zusätzlich zu handgeschriebenen Validierungsoptionen unterstützen wir auch die Standard Schema Spezifikation.

Sie können ein Schema mit einer beliebigen der Bibliotheken definieren, die die Spezifikation implementieren, und es an einen Formular- oder Feldvalidator übergeben.

Unterstützte Bibliotheken umfassen:

  • Zod (v3.24.0 oder höher)
  • Valibot (v1.0.0 oder höher)
  • ArkType (v2.1.20 oder höher)
  • Yup (v1.7.0 oder höher)

Beispiel

angular-ts
import { z } from 'zod'

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="firstName"
      [validators]="{
        onChange: z.string().min(3, 'First name must be at least 3 characters'),
        onChangeAsyncDebounceMs: 500,
        onChangeAsync: firstNameAsyncValidator
      }"
      #firstName="field"
    >
      <!-- ... -->
    </ng-container>
  `,
})
export class AppComponent {
  firstNameAsyncValidator = z.string().refine(
    async (value) => {
      await new Promise((resolve) => setTimeout(resolve, 1000))
      return !value.includes('error')
    },
    {
      message: "No 'error' allowed in first name",
    },
  )

  form = injectForm({
    defaultValues: {
      firstName: '',
    },
    onSubmit({ value }) {
      // Do something with form data
      console.log(value)
    },
  })

  z = z
}
import { z } from 'zod'

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="firstName"
      [validators]="{
        onChange: z.string().min(3, 'First name must be at least 3 characters'),
        onChangeAsyncDebounceMs: 500,
        onChangeAsync: firstNameAsyncValidator
      }"
      #firstName="field"
    >
      <!-- ... -->
    </ng-container>
  `,
})
export class AppComponent {
  firstNameAsyncValidator = z.string().refine(
    async (value) => {
      await new Promise((resolve) => setTimeout(resolve, 1000))
      return !value.includes('error')
    },
    {
      message: "No 'error' allowed in first name",
    },
  )

  form = injectForm({
    defaultValues: {
      firstName: '',
    },
    onSubmit({ value }) {
      // Do something with form data
      console.log(value)
    },
  })

  z = z
}

Reaktivität

@tanstack/angular-form bietet eine Möglichkeit, Formular- und Feldzustandsänderungen über injectStore(this.form, selector) zu abonnieren.

Beispiel

typescript
import { injectForm, injectStore } from '@tanstack/angular-form'

@Component(/*...*/)
class AppComponent {
  form = injectForm(/*...*/)
  canSubmit = injectStore(this.form, (state) => state.canSubmit)
  isSubmitting = injectStore(this.form, (state) => state.isSubmitting)
}
import { injectForm, injectStore } from '@tanstack/angular-form'

@Component(/*...*/)
class AppComponent {
  form = injectForm(/*...*/)
  canSubmit = injectStore(this.form, (state) => state.canSubmit)
  isSubmitting = injectStore(this.form, (state) => state.isSubmitting)
}

Listener

@tanstack/angular-form ermöglicht es Ihnen, auf bestimmte Auslöser (Triggers) zu reagieren und diese zu "beobachten", um Seiteneffekte auszulösen.

Beispiel

angular-ts
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="country"
      [listeners]="{
        onChange: onCountryChange
      }"
      #country="field"
    ></ng-container>
  `,
})

...

onCountryChange: FieldListenerFn<any, any, any, any, string> = ({
    value,
  }) => {
    console.log(`Country changed to: ${value}, resetting province`)
    this.form.setFieldValue('province', '')
  }
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="country"
      [listeners]="{
        onChange: onCountryChange
      }"
      #country="field"
    ></ng-container>
  `,
})

...

onCountryChange: FieldListenerFn<any, any, any, any, string> = ({
    value,
  }) => {
    console.log(`Country changed to: ${value}, resetting province`)
    this.form.setFieldValue('province', '')
  }

Weitere Informationen finden Sie unter Listener

Array-Felder

Array-Felder ermöglichen es Ihnen, eine Liste von Werten innerhalb eines Formulars zu verwalten, z. B. eine Liste von Hobbys. Sie können ein Array-Feld mit der Direktive tanstackField erstellen.

Beim Arbeiten mit Array-Feldern können Sie die Methoden pushValue, removeValue und swapValues des Feldes verwenden, um Werte im Array hinzuzufügen, zu entfernen und zu tauschen.

Beispiel

angular-ts
@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))
    },
  })
}
@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))
    },
  })
}

Dies sind die grundlegenden Konzepte und die Terminologie, die in der @tanstack/angular-form Bibliothek verwendet werden. Das Verständnis dieser Konzepte wird Ihnen helfen, effektiver mit der Bibliothek zu arbeiten und komplexe Formulare einfach zu erstellen.

Unsere Partner
Code Rabbit
Bytes abonnieren

Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.

Bytes

Kein Spam. Jederzeit kündbar.

Bytes abonnieren

Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.

Bytes

Kein Spam. Jederzeit kündbar.