表单组合

TanStack Form 常被批评之处在于其开箱即用的冗长。虽然这可能有助于教学——帮助巩固对 API 的理解——但在生产用例中并非理想选择。

因此,虽然 [tanstackField] 的基本用法实现了 TanStack Form 最强大、最灵活的用法,但我们仍然提供了包装它的 API,使您的应用程序代码更加简洁。

预绑定的字段组件

如果您曾经在 Angular 中使用 TanStack Form 绑定多个输入,您很快就会意识到每个输入需要多少工作量。

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

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <div>
      <ng-container
        [tanstackField]="form"
        name="firstName"
        #firstName="field"
      >
        <label [for]="firstName.api.name">First Name:</label>
        <input
          [id]="firstName.api.name"
          [name]="firstName.api.name"
          [value]="firstName.api.state.value"
          (blur)="firstName.api.handleBlur()"
          (input)="firstName.api.handleChange($any($event).target.value)"
        />
        @if (firstName.api.state.meta.isTouched) {
          @for (error of firstName.api.state.meta.errors; track $index) {
            <div style="color: red">
              {{ error }}
            </div>
          }
        }
        @if (firstName.api.state.meta.isValidating) {
          <p>Validating...</p>
        }
      </ng-container>
    </div>
    <div>
      <ng-container
        [tanstackField]="form"
        name="lastName"
        #lastName="field"
      >
        <label [for]="lastName.api.name">Last Name:</label>
        <input
          [id]="lastName.api.name"
          [name]="lastName.api.name"
          [value]="lastName.api.state.value"
          (blur)="lastName.api.handleBlur()"
          (input)="lastName.api.handleChange($any($event).target.value)"
        />
        @if (lastName.api.state.meta.isTouched) {
          @for (error of lastName.api.state.meta.errors; track $index) {
            <div style="color: red">
              {{ error }}
            </div>
          }
        }
        @if (lastName.api.state.meta.isValidating) {
          <p>Validating...</p>
        }
      </ng-container>
    </div>
  `,
})
export class AppComponent {
  form = injectForm({
    defaultValues: {
      firstName: '',
      lastName: '',
    },
    onSubmit({ value }) {
      // Do something with form data
      console.log(value)
    },
  })
}
import { Component } from '@angular/core'
import { TanStackField, injectForm, injectStore } from '@tanstack/angular-form'

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField],
  template: `
    <div>
      <ng-container
        [tanstackField]="form"
        name="firstName"
        #firstName="field"
      >
        <label [for]="firstName.api.name">First Name:</label>
        <input
          [id]="firstName.api.name"
          [name]="firstName.api.name"
          [value]="firstName.api.state.value"
          (blur)="firstName.api.handleBlur()"
          (input)="firstName.api.handleChange($any($event).target.value)"
        />
        @if (firstName.api.state.meta.isTouched) {
          @for (error of firstName.api.state.meta.errors; track $index) {
            <div style="color: red">
              {{ error }}
            </div>
          }
        }
        @if (firstName.api.state.meta.isValidating) {
          <p>Validating...</p>
        }
      </ng-container>
    </div>
    <div>
      <ng-container
        [tanstackField]="form"
        name="lastName"
        #lastName="field"
      >
        <label [for]="lastName.api.name">Last Name:</label>
        <input
          [id]="lastName.api.name"
          [name]="lastName.api.name"
          [value]="lastName.api.state.value"
          (blur)="lastName.api.handleBlur()"
          (input)="lastName.api.handleChange($any($event).target.value)"
        />
        @if (lastName.api.state.meta.isTouched) {
          @for (error of lastName.api.state.meta.errors; track $index) {
            <div style="color: red">
              {{ error }}
            </div>
          }
        }
        @if (lastName.api.state.meta.isValidating) {
          <p>Validating...</p>
        }
      </ng-container>
    </div>
  `,
})
export class AppComponent {
  form = injectForm({
    defaultValues: {
      firstName: '',
      lastName: '',
    },
    onSubmit({ value }) {
      // Do something with form data
      console.log(value)
    },
  })
}

这在功能上是正确的,但引入了大量重复的模板化行为。相反,让我们将错误处理、标签到输入的绑定以及其他重复的逻辑移到一个组件中。

angular-ts
import {injectField} from '@tanstack/angular-form'

@Component({
  selector: 'app-text-field',
  standalone: true,
  template: `
    <label [for]="field.api.name">{{ label() }}</label>
    <input
      [id]="field.api.name"
      [name]="field.api.name"
      [value]="field.api.state.value"
      (blur)="field.api.handleBlur()"
      (input)="field.api.handleChange($any($event).target.value)"
    />
    @if (field.api.state.meta.isTouched) {
      @for (error of field.api.state.meta.errors; track $index) {
        <div style="color: red">
          {{ error }}
        </div>
      }
    }
    @if (field.api.state.meta.isValidating) {
      <p>Validating...</p>
    }
  `,
})
export class AppTextField {
  label = input.required<string>()
  // This API requires another part to it from the parent component
  field = injectField<string>()
}
import {injectField} from '@tanstack/angular-form'

@Component({
  selector: 'app-text-field',
  standalone: true,
  template: `
    <label [for]="field.api.name">{{ label() }}</label>
    <input
      [id]="field.api.name"
      [name]="field.api.name"
      [value]="field.api.state.value"
      (blur)="field.api.handleBlur()"
      (input)="field.api.handleChange($any($event).target.value)"
    />
    @if (field.api.state.meta.isTouched) {
      @for (error of field.api.state.meta.errors; track $index) {
        <div style="color: red">
          {{ error }}
        </div>
      }
    }
    @if (field.api.state.meta.isValidating) {
      <p>Validating...</p>
    }
  `,
})
export class AppTextField {
  label = input.required<string>()
  // This API requires another part to it from the parent component
  field = injectField<string>()
}

injectField 接受一个泛型来定义 field.state.value 的类型。

因此,例如,一个数字文本字段将表示为 injectField<number>

现在,我们可以使用 TanStackAppField 指令(tanstack-app-field)来 提供与此输入关联的预期字段。

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

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField, TanStackAppField, AppTextField],
  template: `
    <div>
      <app-text-field
        label="First name:"
        tanstack-app-field
        [tanstackField]="form"
        name="firstName"
      />
    </div>
    <div>
      <app-text-field
        label="Last name:"
        tanstack-app-field
        [tanstackField]="form"
        name="lastName"
      />
    </div>
  `,
})
export class AppComponent {
  form = injectForm({
    defaultValues: {
      firstName: '',
      lastName: '',
    },
    onSubmit({ value }) {
      // Do something with form data
      console.log(value)
    },
  })
}
import { Component } from '@angular/core'
import {
  TanStackAppField,
  TanStackField,
  injectForm,
} from '@tanstack/angular-form'

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TanStackField, TanStackAppField, AppTextField],
  template: `
    <div>
      <app-text-field
        label="First name:"
        tanstack-app-field
        [tanstackField]="form"
        name="firstName"
      />
    </div>
    <div>
      <app-text-field
        label="Last name:"
        tanstack-app-field
        [tanstackField]="form"
        name="lastName"
      />
    </div>
  `,
})
export class AppComponent {
  form = injectForm({
    defaultValues: {
      firstName: '',
      lastName: '',
    },
    onSubmit({ value }) {
      // Do something with form data
      console.log(value)
    },
  })
}

在这里,tanstack-app-field 指令获取 [tanstackField] 的属性,并将其 提供app-text-field,以便它们可以作为组件更容易地使用。

我们的合作伙伴
Code Rabbit
订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。

订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。