Formular- und Feldvalidierung

Im Kern der Funktionalität von TanStack Form steht das Konzept der Validierung. TanStack Form macht die Validierung hochgradig anpassbar

  • Sie können steuern, wann die Validierung durchgeführt werden soll (bei Änderung, bei Eingabe, beim Verlassen, bei Übermittlung...)
  • Validierungsregeln können auf Feld- oder Formularebene definiert werden
  • Die Validierung kann synchron oder asynchron sein (z. B. als Ergebnis eines API-Aufrufs)

Wann wird die Validierung durchgeführt?

Es liegt an Ihnen! Die Direktive [tanstackField] akzeptiert einige Rückruffunktionen als Props wie onChange oder onBlur. Diese Rückruffunktionen erhalten den aktuellen Wert des Feldes sowie das fieldAPI-Objekt, sodass Sie die Validierung durchführen können. Wenn Sie einen Validierungsfehler finden, geben Sie einfach die Fehlermeldung als String zurück, und sie wird in field.api.state.meta.errors verfügbar sein.

Hier ist ein Beispiel

angular-ts
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{
        onChange: ageValidator
      }"
      #age="field"
    >
      <label [for]="age.api.name">Age:</label>
      <input
        [id]="age.api.name"
        [name]="age.api.name"
        [value]="age.api.state.value"
        type="number"
        (input)="age.api.handleChange($any($event).target.valueAsNumber)"
      />
      @if (age.api.state.meta.errors) {
        <em role="alert">{{ age.api.state.meta.errors.join(', ') }}</em>
      }
    </ng-container>
  `,
})
export class AppComponent {
  ageValidator: FieldValidateFn<any, any, any, any, number> = ({ value }) =>
    value < 13 ? 'You must be 13 to make an account' : undefined

  // ...
}
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{
        onChange: ageValidator
      }"
      #age="field"
    >
      <label [for]="age.api.name">Age:</label>
      <input
        [id]="age.api.name"
        [name]="age.api.name"
        [value]="age.api.state.value"
        type="number"
        (input)="age.api.handleChange($any($event).target.valueAsNumber)"
      />
      @if (age.api.state.meta.errors) {
        <em role="alert">{{ age.api.state.meta.errors.join(', ') }}</em>
      }
    </ng-container>
  `,
})
export class AppComponent {
  ageValidator: FieldValidateFn<any, any, any, any, number> = ({ value }) =>
    value < 13 ? 'You must be 13 to make an account' : undefined

  // ...
}

Im obigen Beispiel wird die Validierung bei jedem Tastendruck (onChange) durchgeführt. Wenn wir stattdessen möchten, dass die Validierung beim Verlassen des Feldes erfolgt, würden wir den obigen Code wie folgt ändern

angular-ts
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{
        onBlur: ageValidator
      }"
      #age="field"
    >
      <label [for]="age.api.name">Age:</label>
      <!-- We always need to implement onChange, so that TanStack Form receives the changes -->
      <!-- Listen to the onBlur event on the field -->
      <input
        [id]="age.api.name"
        [name]="age.api.name"
        [value]="age.api.state.value"
        type="number"
        (blur)='age.api.handleBlur()'
        (input)="age.api.handleChange($any($event).target.valueAsNumber)"
      />
      @if (age.api.state.meta.errors) {
        <em role="alert">{{ age.api.state.meta.errors.join(', ') }}</em>
      }
    </ng-container>
  `,
})
export class AppComponent {
  ageValidator: FieldValidateFn<any, any, any, any, number> = ({ value }) =>
    value < 13 ? 'You must be 13 to make an account' : undefined

  // ...
}
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{
        onBlur: ageValidator
      }"
      #age="field"
    >
      <label [for]="age.api.name">Age:</label>
      <!-- We always need to implement onChange, so that TanStack Form receives the changes -->
      <!-- Listen to the onBlur event on the field -->
      <input
        [id]="age.api.name"
        [name]="age.api.name"
        [value]="age.api.state.value"
        type="number"
        (blur)='age.api.handleBlur()'
        (input)="age.api.handleChange($any($event).target.valueAsNumber)"
      />
      @if (age.api.state.meta.errors) {
        <em role="alert">{{ age.api.state.meta.errors.join(', ') }}</em>
      }
    </ng-container>
  `,
})
export class AppComponent {
  ageValidator: FieldValidateFn<any, any, any, any, number> = ({ value }) =>
    value < 13 ? 'You must be 13 to make an account' : undefined

  // ...
}

Sie können also steuern, wann die Validierung durchgeführt wird, indem Sie die gewünschte Rückruffunktion implementieren. Sie können sogar zu verschiedenen Zeiten unterschiedliche Validierungsstücke durchführen

angular-ts
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{
        onChange: ageValidator,
        onBlur: minimumAgeValidator
      }"
      #age="field"
    >
      <label [for]="age.api.name">Age:</label>
      <!-- We always need to implement onChange, so that TanStack Form receives the changes -->
      <!-- Listen to the onBlur event on the field -->
      <input
        [id]="age.api.name"
        [name]="age.api.name"
        [value]="age.api.state.value"
        type="number"
        (blur)="age.api.handleBlur()"
        (input)="age.api.handleChange($any($event).target.valueAsNumber)"
      />
      @if (!age.api.state.meta.isValid) {
        <em role="alert">{{ age.api.state.meta.errors.join(', ') }}</em>
      }
    </ng-container>
  `,
})
export class AppComponent {
  ageValidator: FieldValidateFn<any, any, any, any, number> = ({ value }) =>
    value < 13 ? 'You must be 13 to make an account' : undefined

  minimumAgeValidator: FieldValidateFn<any, any, any, any, number> = ({
    value,
  }) => (value < 0 ? 'Invalid value' : undefined)

  // ...
}
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{
        onChange: ageValidator,
        onBlur: minimumAgeValidator
      }"
      #age="field"
    >
      <label [for]="age.api.name">Age:</label>
      <!-- We always need to implement onChange, so that TanStack Form receives the changes -->
      <!-- Listen to the onBlur event on the field -->
      <input
        [id]="age.api.name"
        [name]="age.api.name"
        [value]="age.api.state.value"
        type="number"
        (blur)="age.api.handleBlur()"
        (input)="age.api.handleChange($any($event).target.valueAsNumber)"
      />
      @if (!age.api.state.meta.isValid) {
        <em role="alert">{{ age.api.state.meta.errors.join(', ') }}</em>
      }
    </ng-container>
  `,
})
export class AppComponent {
  ageValidator: FieldValidateFn<any, any, any, any, number> = ({ value }) =>
    value < 13 ? 'You must be 13 to make an account' : undefined

  minimumAgeValidator: FieldValidateFn<any, any, any, any, number> = ({
    value,
  }) => (value < 0 ? 'Invalid value' : undefined)

  // ...
}

Im obigen Beispiel validieren wir unterschiedliche Dinge für dasselbe Feld zu unterschiedlichen Zeiten (bei jedem Tastendruck und beim Verlassen des Feldes). Da field.state.meta.errors ein Array ist, werden alle relevanten Fehler zu einem bestimmten Zeitpunkt angezeigt. Sie können auch field.state.meta.errorMap verwenden, um Fehler basierend darauf zu erhalten, *wann* die Validierung durchgeführt wurde (onChange, onBlur etc.). Mehr Informationen zur Anzeige von Fehlern weiter unten.

Fehler anzeigen

Sobald Ihre Validierung eingerichtet ist, können Sie die Fehler aus einem Array abbilden, um sie in Ihrer Benutzeroberfläche anzuzeigen

angular-ts
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{
        onChange: ageValidator
      }"
      #age="field"
    >
      <!-- ... -->
      @if (age.api.state.meta.errors) {
        <em role="alert">{{ age.api.state.meta.errors.join(', ') }}</em>
      }
    </ng-container>
  `,
})
export class AppComponent {
  ageValidator: FieldValidateFn<any, any, any, any, number> = ({ value }) =>
    value < 13 ? 'You must be 13 to make an account' : undefined

  // ...
}
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{
        onChange: ageValidator
      }"
      #age="field"
    >
      <!-- ... -->
      @if (age.api.state.meta.errors) {
        <em role="alert">{{ age.api.state.meta.errors.join(', ') }}</em>
      }
    </ng-container>
  `,
})
export class AppComponent {
  ageValidator: FieldValidateFn<any, any, any, any, number> = ({ value }) =>
    value < 13 ? 'You must be 13 to make an account' : undefined

  // ...
}

Oder verwenden Sie die Eigenschaft errorMap, um auf den spezifischen Fehler zuzugreifen, den Sie suchen

angular-ts
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{
        onChange: ageValidator
      }"
      #age="field"
    >
      <!-- ... -->
      @if (age.api.state.meta.errorMap['onChange']) {
        <em role="alert">{{ age.api.state.meta.errorMap['onChange'] }}</em>
      }
    </ng-container>
  `,
})
export class AppComponent {
  ageValidator: FieldValidateFn<any, any, any, any, number> = ({ value }) =>
    value < 13 ? 'You must be 13 to make an account' : undefined

  // ...
}
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{
        onChange: ageValidator
      }"
      #age="field"
    >
      <!-- ... -->
      @if (age.api.state.meta.errorMap['onChange']) {
        <em role="alert">{{ age.api.state.meta.errorMap['onChange'] }}</em>
      }
    </ng-container>
  `,
})
export class AppComponent {
  ageValidator: FieldValidateFn<any, any, any, any, number> = ({ value }) =>
    value < 13 ? 'You must be 13 to make an account' : undefined

  // ...
}

Es ist erwähnenswert, dass unser errors Array und die errorMap den von den Validatoren zurückgegebenen Typen entsprechen. Das bedeutet

angular-ts
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{
        onChange: ageValidator
      }"
      #age="field"
    >
      <!-- ... -->
      <!-- errorMap.onChange is type `{isOldEnough: false} | undefined` -->
      <!-- meta.errors is type `Array<{isOldEnough: false} | undefined>` -->
      @if (!age.api.state.meta.errorMap['onChange']?.isOldEnough) {
        <em role="alert">The user is not old enough</em>
      }
    </ng-container>
  `,
})
export class AppComponent {
  ageValidator: FieldValidateFn<any, any, any, any, number> = ({ value }) =>
    value < 13 ? 'You must be 13 to make an account' : undefined

  // ...
}
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{
        onChange: ageValidator
      }"
      #age="field"
    >
      <!-- ... -->
      <!-- errorMap.onChange is type `{isOldEnough: false} | undefined` -->
      <!-- meta.errors is type `Array<{isOldEnough: false} | undefined>` -->
      @if (!age.api.state.meta.errorMap['onChange']?.isOldEnough) {
        <em role="alert">The user is not old enough</em>
      }
    </ng-container>
  `,
})
export class AppComponent {
  ageValidator: FieldValidateFn<any, any, any, any, number> = ({ value }) =>
    value < 13 ? 'You must be 13 to make an account' : undefined

  // ...
}

Validierung auf Feld- vs. Formularebene

Wie oben gezeigt, akzeptiert jede [tanstackField] eigene Validierungsregeln über die Rückruffunktionen onChange, onBlur etc. Es ist auch möglich, Validierungsregeln auf Formularebene (im Gegensatz zu Feld für Feld) zu definieren, indem ähnliche Rückruffunktionen an die Funktion injectForm() übergeben werden.

Beispiel

angular-ts
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <div>
      <ng-container [tanstackField]="form" name="age" #age="field">
        <!-- ... -->
        @if (formErrorMap().onChange) {
          <div>
            <em
              >There was an error on the form: {{ formErrorMap().onChange }}</em
            >
          </div>
        }
        <!-- ... -->
      </ng-container>
    </div>
  `,
})
export class AppComponent {
  form = injectForm({
    defaultValues: {
      age: 0,
    },
    onSubmit({ value }) {
      console.log(value)
    },
    validators: {
      // Add validators to the form the same way you would add them to a field
      onChange({ value }) {
        if (value.age < 13) {
          return 'Must be 13 or older to sign'
        }
        return undefined
      },
    },
  })

  // Subscribe to the form's error map so that updates to it will render
  formErrorMap = injectStore(this.form, (state) => state.errorMap)
}
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <div>
      <ng-container [tanstackField]="form" name="age" #age="field">
        <!-- ... -->
        @if (formErrorMap().onChange) {
          <div>
            <em
              >There was an error on the form: {{ formErrorMap().onChange }}</em
            >
          </div>
        }
        <!-- ... -->
      </ng-container>
    </div>
  `,
})
export class AppComponent {
  form = injectForm({
    defaultValues: {
      age: 0,
    },
    onSubmit({ value }) {
      console.log(value)
    },
    validators: {
      // Add validators to the form the same way you would add them to a field
      onChange({ value }) {
        if (value.age < 13) {
          return 'Must be 13 or older to sign'
        }
        return undefined
      },
    },
  })

  // Subscribe to the form's error map so that updates to it will render
  formErrorMap = injectStore(this.form, (state) => state.errorMap)
}

Festlegen von Feldfehlern aus den Validatoren des Formulars

Sie können Fehler auf den Feldern aus den Validatoren des Formulars festlegen. Ein häufiger Anwendungsfall dafür ist die Validierung aller Felder bei der Übermittlung durch Aufruf eines einzelnen API-Endpunkts im onSubmitAsync Validator des Formulars.

angular-ts
@Component({
  selector: 'app-root',
  imports: [TanStackField],
  template: `
    <form (submit)="handleSubmit($event)">
      <div>
        <ng-container
          [tanstackField]="form"
          name="age"
          #ageField="field"
        >
          <label [for]="ageField.api.name">Age:</label>
          <input
            type="number"
            [name]="ageField.api.name"
            [value]="ageField.api.state.value"
            (blur)="ageField.api.handleBlur()"
            (input)="ageField.api.handleChange($any($event).target.valueAsNumber)"
          />
          @if (ageField.api.state.meta.errors.length > 0) {
            <em role="alert">{{ ageField.api.state.meta.errors.join(', ') }}</em>
          }
        </ng-container>
      </div>
      <button type="submit">Submit</button>
    </form>
  `,
})

export class AppComponent {
  form = injectForm({
    defaultValues: {
      age: 0,
      socials: [],
      details: {
        email: '',
      },
    },
    validators: {
      onSubmitAsync: async ({ value }) => {
        // Validate the value on the server
        const hasErrors = await verifyDataOnServer(value)
        if (hasErrors) {
          return {
            form: 'Invalid data', // The `form` key is optional
            fields: {
              age: 'Must be 13 or older to sign',
              // Set errors on nested fields with the field's name
              'socials[0].url': 'The provided URL does not exist',
              'details.email': 'An email is required',
            },
          };
        }

        return null;
      },
    },
  });

  handleSubmit(event: SubmitEvent) {
    event.preventDefault();
    event.stopPropagation();
    this.form.handleSubmit();
  }
}
@Component({
  selector: 'app-root',
  imports: [TanStackField],
  template: `
    <form (submit)="handleSubmit($event)">
      <div>
        <ng-container
          [tanstackField]="form"
          name="age"
          #ageField="field"
        >
          <label [for]="ageField.api.name">Age:</label>
          <input
            type="number"
            [name]="ageField.api.name"
            [value]="ageField.api.state.value"
            (blur)="ageField.api.handleBlur()"
            (input)="ageField.api.handleChange($any($event).target.valueAsNumber)"
          />
          @if (ageField.api.state.meta.errors.length > 0) {
            <em role="alert">{{ ageField.api.state.meta.errors.join(', ') }}</em>
          }
        </ng-container>
      </div>
      <button type="submit">Submit</button>
    </form>
  `,
})

export class AppComponent {
  form = injectForm({
    defaultValues: {
      age: 0,
      socials: [],
      details: {
        email: '',
      },
    },
    validators: {
      onSubmitAsync: async ({ value }) => {
        // Validate the value on the server
        const hasErrors = await verifyDataOnServer(value)
        if (hasErrors) {
          return {
            form: 'Invalid data', // The `form` key is optional
            fields: {
              age: 'Must be 13 or older to sign',
              // Set errors on nested fields with the field's name
              'socials[0].url': 'The provided URL does not exist',
              'details.email': 'An email is required',
            },
          };
        }

        return null;
      },
    },
  });

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

Etwas erwähnenswert ist, dass wenn Sie eine Formularvalidierungsfunktion haben, die einen Fehler zurückgibt, dieser Fehler durch die feldspezifische Validierung überschrieben werden kann.

Das bedeutet

angular-ts
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
      <div>
        <ng-container
         [tanstackField]="form"
        name="age"
       #ageField="field"
      [validators]="{
       onChange: fieldValidator
    }"
 >
  <input type="number" [value]="ageField.api.state.value"
  (input)="ageField.api.handleChange($any($event).target.valueAsNumber)"
  />
   @if (ageField.api.state.meta.errors.length > 0) {
      <em role="alert">{{ ageField.api.state.meta.errors.join(', ') }}</em>
    }
  </ng-container>
</div>
`,
})
export class AppComponent {
  form = injectForm({
    defaultValues: {
      age: 0,
    },
    validators: {
      onChange: ({ value }) => {
        return {
          fields: {
            age: value.age < 12 ? 'Too young!' : undefined,
          },
        };
      },
    },
  });

  fieldValidator: FieldValidateFn<any, any, number> = ({ value }) =>
    value % 2 === 0 ? 'Must be odd!' : undefined;
}
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
      <div>
        <ng-container
         [tanstackField]="form"
        name="age"
       #ageField="field"
      [validators]="{
       onChange: fieldValidator
    }"
 >
  <input type="number" [value]="ageField.api.state.value"
  (input)="ageField.api.handleChange($any($event).target.valueAsNumber)"
  />
   @if (ageField.api.state.meta.errors.length > 0) {
      <em role="alert">{{ ageField.api.state.meta.errors.join(', ') }}</em>
    }
  </ng-container>
</div>
`,
})
export class AppComponent {
  form = injectForm({
    defaultValues: {
      age: 0,
    },
    validators: {
      onChange: ({ value }) => {
        return {
          fields: {
            age: value.age < 12 ? 'Too young!' : undefined,
          },
        };
      },
    },
  });

  fieldValidator: FieldValidateFn<any, any, number> = ({ value }) =>
    value % 2 === 0 ? 'Must be odd!' : undefined;
}

Zeigt nur 'Must be odd! an, auch wenn der Fehler 'Too young!' von der Formularvalidierung zurückgegeben wird.

Asynchrone funktionale Validierung

Obwohl wir vermuten, dass die meisten Validierungen synchron sein werden, gibt es viele Fälle, in denen ein Netzwerkanruf oder eine andere asynchrone Operation für die Validierung nützlich wäre.

Um dies zu erreichen, haben wir dedizierte onChangeAsync, onBlurAsync und andere Methoden, die zur Validierung verwendet werden können

angular-ts
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{ onChangeAsync: ageValidator }"
      #age="field"
    >
      <label [for]="age.api.name">Last Name:</label>
      <input
        [id]="age.api.name"
        [name]="age.api.name"
        [value]="age.api.state.value"
        type="number"
        (input)="age.api.handleChange($any($event).target.valueAsNumber)"
      />
      @if (age.api.state.meta.errors) {
        <em role="alert">{{ age.api.state.meta.errors.join(', ') }}</em>
      }
    </ng-container>
  `,
})
export class AppComponent {
  ageValidator: FieldValidateAsyncFn<any, string, number> = async ({
    value,
  }) => {
    await new Promise((resolve) => setTimeout(resolve, 1000))
    return value < 13 ? 'You must be 13 to make an account' : undefined
  }

  // ...
}
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{ onChangeAsync: ageValidator }"
      #age="field"
    >
      <label [for]="age.api.name">Last Name:</label>
      <input
        [id]="age.api.name"
        [name]="age.api.name"
        [value]="age.api.state.value"
        type="number"
        (input)="age.api.handleChange($any($event).target.valueAsNumber)"
      />
      @if (age.api.state.meta.errors) {
        <em role="alert">{{ age.api.state.meta.errors.join(', ') }}</em>
      }
    </ng-container>
  `,
})
export class AppComponent {
  ageValidator: FieldValidateAsyncFn<any, string, number> = async ({
    value,
  }) => {
    await new Promise((resolve) => setTimeout(resolve, 1000))
    return value < 13 ? 'You must be 13 to make an account' : undefined
  }

  // ...
}

Synchrone und asynchrone Validierungen können koexistieren. Es ist beispielsweise möglich, sowohl onBlur als auch onBlurAsync für dasselbe Feld zu definieren

angular-ts
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{ onBlur: ensureAge13, onBlurAsync: ensureOlderAge }"
      #age="field"
    >
      <label [for]="age.api.name">Last Name:</label>
      <input
        [id]="age.api.name"
        [name]="age.api.name"
        [value]="age.api.state.value"
        type='number'
        (blur)="age.api.handleBlur()"
        (input)="age.api.handleChange($any($event).target.value)"
      />
      @if (age.api.state.meta.errors) {
        <em role="alert">{{ age.api.state.meta.errors.join(', ') }}</em>
      }
    </ng-container>
  `,
})
export class AppComponent {
  ensureAge13: FieldValidateFn<any, any, any, any, number> = ({ value }) =>
    value < 13 ? 'You must be at least 13' : undefined

  ensureOlderAge: FieldValidateAsyncFn<any, string, number> = async ({
    value,
  }) => {
    const currentAge = await fetchCurrentAgeOnProfile()
    return value < currentAge ? 'You can only increase the age' : undefined
  }

  // ...
}
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{ onBlur: ensureAge13, onBlurAsync: ensureOlderAge }"
      #age="field"
    >
      <label [for]="age.api.name">Last Name:</label>
      <input
        [id]="age.api.name"
        [name]="age.api.name"
        [value]="age.api.state.value"
        type='number'
        (blur)="age.api.handleBlur()"
        (input)="age.api.handleChange($any($event).target.value)"
      />
      @if (age.api.state.meta.errors) {
        <em role="alert">{{ age.api.state.meta.errors.join(', ') }}</em>
      }
    </ng-container>
  `,
})
export class AppComponent {
  ensureAge13: FieldValidateFn<any, any, any, any, number> = ({ value }) =>
    value < 13 ? 'You must be at least 13' : undefined

  ensureOlderAge: FieldValidateAsyncFn<any, string, number> = async ({
    value,
  }) => {
    const currentAge = await fetchCurrentAgeOnProfile()
    return value < currentAge ? 'You can only increase the age' : undefined
  }

  // ...
}

Die synchrone Validierungsmethode (onBlur) wird zuerst ausgeführt, und die asynchrone Methode (onBlurAsync) wird nur ausgeführt, wenn die synchrone Methode (onBlur) erfolgreich war. Um dieses Verhalten zu ändern, setzen Sie die Option asyncAlways auf true, und die asynchrone Methode wird unabhängig vom Ergebnis der synchronen Methode ausgeführt.

Integrierte Verzögerung (Debouncing)

Während asynchrone Aufrufe der richtige Weg sind, wenn gegen die Datenbank validiert wird, ist das Ausführen einer Netzwerkanfrage bei jedem Tastendruck eine gute Möglichkeit, Ihre Datenbank zu überlasten (DDOS).

Stattdessen ermöglichen wir eine einfache Methode zum Verzögern Ihrer async Aufrufe, indem wir eine einzige Eigenschaft hinzufügen

angular-html
<ng-container
  [tanstackField]="form"
  name="age"
  asyncDebounceMs={500}
  [validators]="{ onChangeAsync: someValidator }"
  #age="field"
>
  <!-- ... -->
</ng-container>
<ng-container
  [tanstackField]="form"
  name="age"
  asyncDebounceMs={500}
  [validators]="{ onChangeAsync: someValidator }"
  #age="field"
>
  <!-- ... -->
</ng-container>

Dies verzögert jeden asynchronen Aufruf um 500 ms. Sie können diese Eigenschaft sogar pro Validierungseigenschaft überschreiben

angular-html
<ng-container
  [tanstackField]="form"
  name="age"
  [validators]="{
    onChangeAsyncDebounceMs: 1500,
    onChangeAsync: someValidator,
    onBlurAsync: otherValidator
  }"
  #age="field"
>
  <!-- ... -->
</ng-container>
<ng-container
  [tanstackField]="form"
  name="age"
  [validators]="{
    onChangeAsyncDebounceMs: 1500,
    onChangeAsync: someValidator,
    onBlurAsync: otherValidator
  }"
  #age="field"
>
  <!-- ... -->
</ng-container>

Dies führt onChangeAsync alle 1500 ms aus, während onBlurAsync alle 500 ms ausgeführt wird.

Validierung durch Schema-Bibliotheken

Während Funktionen mehr Flexibilität und Anpassungsmöglichkeiten für Ihre Validierung bieten, können sie etwas umständlich sein. Um dieses Problem zu lösen, gibt es Bibliotheken, die eine schema-basierte Validierung bieten, um eine Kurzschrift und Typsicherheit bei der Validierung erheblich zu vereinfachen. Sie können auch ein einzelnes Schema für Ihr gesamtes Formular definieren und es auf Formularebene übergeben, Fehler werden automatisch an die Felder weitergeleitet.

Standard-Schema-Bibliotheken

TanStack Form unterstützt nativ alle Bibliotheken, die der Standard Schema-Spezifikation folgen, insbesondere

Hinweis: Stellen Sie sicher, dass Sie die neueste Version der Schema-Bibliotheken verwenden, da ältere Versionen Standard Schema möglicherweise noch nicht unterstützen.

Die Validierung liefert Ihnen keine transformierten Werte. Weitere Informationen finden Sie unter Übermittlungsbehandlung.

Um Schemata aus diesen Bibliotheken zu verwenden, können Sie sie an die validators Props übergeben, so wie Sie es auch mit einer benutzerdefinierten Funktion tun würden

angular-ts
import { z } from 'zod'

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{
        onChange: z.number().gte(13, 'You must be 13 to make an account'),
      }"
      #age="field"
    >
      <!-- ... -->
    </ng-container>
  `,
})
export class AppComponent {
  form = injectForm({
    // ...
   })

  z = z

  // ...
}
import { z } from 'zod'

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{
        onChange: z.number().gte(13, 'You must be 13 to make an account'),
      }"
      #age="field"
    >
      <!-- ... -->
    </ng-container>
  `,
})
export class AppComponent {
  form = injectForm({
    // ...
   })

  z = z

  // ...
}

Asynchrone Validierungen auf Formular- und Feld-Ebene werden ebenfalls unterstützt

angular-ts
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{
        onChange: z.number().gte(13, 'You must be 13 to make an account'),
        onChangeAsyncDebounceMs: 500,
        onChangeAsync: increaseAge
      }"
      #age="field"
    >
      <!-- ... -->
    </ng-container>
  `,
})
export class AppComponent {
  increaseAge = z.number().refine(
    async (value) => {
      const currentAge = await fetchCurrentAgeOnProfile()
      return value >= currentAge
    },
    {
      message: 'You can only increase the age',
    },
  )

  // ...
}
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{
        onChange: z.number().gte(13, 'You must be 13 to make an account'),
        onChangeAsyncDebounceMs: 500,
        onChangeAsync: increaseAge
      }"
      #age="field"
    >
      <!-- ... -->
    </ng-container>
  `,
})
export class AppComponent {
  increaseAge = z.number().refine(
    async (value) => {
      const currentAge = await fetchCurrentAgeOnProfile()
      return value >= currentAge
    },
    {
      message: 'You can only increase the age',
    },
  )

  // ...
}

Wenn Sie noch mehr Kontrolle über Ihre Standard-Schema-Validierung benötigen, können Sie ein Standard-Schema mit einer Callback-Funktion wie folgt kombinieren

angular-ts
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{ onChangeAsync: ageValidator }"
      #age="field"
    >
      <!-- ... -->
    </ng-container>
  `,
})
export class AppComponent {
  ageValidator: FieldValidateAsyncFn<any, string, number> = async ({
    value,
    fieldApi,
  }) => {
    const errors = fieldApi.parseValueWithSchema(
      z.number().gte(13, 'You must be 13 to make an account'),
    )
    if (errors) return errors

    // continue with your validation
  }

  // ...
}
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <ng-container
      [tanstackField]="form"
      name="age"
      [validators]="{ onChangeAsync: ageValidator }"
      #age="field"
    >
      <!-- ... -->
    </ng-container>
  `,
})
export class AppComponent {
  ageValidator: FieldValidateAsyncFn<any, string, number> = async ({
    value,
    fieldApi,
  }) => {
    const errors = fieldApi.parseValueWithSchema(
      z.number().gte(13, 'You must be 13 to make an account'),
    )
    if (errors) return errors

    // continue with your validation
  }

  // ...
}

Verhindern der Übermittlung ungültiger Formulare

Die Rückruffunktionen onChange, onBlur usw. werden auch bei der Übermittlung des Formulars ausgeführt, und die Übermittlung wird blockiert, wenn das Formular ungültig ist.

Das Formularstatus-Objekt verfügt über ein Flag canSubmit, das `false` ist, wenn ein Feld ungültig ist und das Formular berührt wurde (canSubmit ist `true`, bis das Formular berührt wurde, auch wenn einige Felder "technisch" ungültig sind, basierend auf ihren onChange/onBlur Props).

Sie können sich über injectStore daran abonnieren und den Wert verwenden, um beispielsweise den Sende-Button zu deaktivieren, wenn das Formular ungültig ist (in der Praxis sind deaktivierte Buttons nicht zugänglich, verwenden Sie stattdessen aria-disabled).

angular-ts
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <!-- ... -->
    <button type="submit" [disabled]="!canSubmit()">
      {{ isSubmitting() ? '...' : 'Submit' }}
    </button>
  `,
})
export class AppComponent {
  canSubmit = injectStore(this.form, (state) => state.canSubmit)
  isSubmitting = injectStore(this.form, (state) => state.isSubmitting)

  // ...
}
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <!-- ... -->
    <button type="submit" [disabled]="!canSubmit()">
      {{ isSubmitting() ? '...' : 'Submit' }}
    </button>
  `,
})
export class AppComponent {
  canSubmit = injectStore(this.form, (state) => state.canSubmit)
  isSubmitting = injectStore(this.form, (state) => state.isSubmitting)

  // ...
}
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.