本页将介绍 `@tanstack/angular-form` 库使用的基本概念和术语。熟悉这些概念将有助于您更好地理解和使用该库。
表单实例 (Form Instance) 是代表单个表单的对象,并提供了操作表单的方法和属性。您可以使用 `injectForm` 函数来创建表单实例。该钩子接受一个包含 `onSubmit` 函数的对象,当表单提交时会调用此函数。
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) 代表单个表单输入元素,例如文本输入或复选框。字段使用 `tanstackField` 指令创建。该指令接受一个 `name` 属性,该属性应与表单默认值中的键匹配。它还通过模板变量暴露了一个名为 `field` 的指令内部实例,用于访问字段的内部数据。
示例
<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>
每个字段都有自己的状态,包括其当前值、验证状态、错误消息和其他元数据。您可以使用 `fieldApi.state` 属性来访问字段的状态。
示例
const {
value,
meta: { errors, isValidating },
} = field.state
const {
value,
meta: { errors, isValidating },
} = field.state
元数据中有四种状态可能有助于了解用户如何与字段进行交互
const { isTouched, isDirty, isPristine, isBlurred } = field.state.meta
const { isTouched, isDirty, isPristine, isBlurred } = field.state.meta
非持久性 dirty 状态
持久性 dirty 状态
我们选择了持久性“dirty”状态模型。为了也支持非持久性“dirty”状态,我们引入了一个额外的标志
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
字段 API (Field API) 是在创建字段时通过 `tanstackField.api` 属性访问的对象。它提供了操作字段状态的方法。
示例
<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)"
/>
`@tanstack/angular-form` 开箱即用地提供了同步和异步验证。可以使用 `validators` 属性将验证函数传递给 `tanstackField` 指令。
示例
@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)
},
})
}
除了手动编写的验证选项外,我们还支持 Standard Schema 规范。
您可以使用任何实现该规范的库来定义一个模式,并将其传递给表单或字段验证器。
支持的库包括
示例
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
}
`@tanstack/angular-form` 提供了一种通过 `injectStore(this.form, selector)` 订阅表单和字段状态变化的方式。
示例
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)
}
`@tanstack/angular-form` 允许您响应特定触发器并“监听”它们以分发副作用。
示例
@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', '')
}
更多信息可在 监听器 (Listeners) 找到。
数组字段 (Array fields) 允许您在表单中管理值列表,例如爱好列表。您可以使用 `tanstackField` 指令创建数组字段。
在使用数组字段时,您可以使用字段的 `pushValue`、`removeValue` 和 `swapValues` 方法来添加、删除和交换数组中的值。
示例
@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))
},
})
}
以上是 `@tanstack/angular-form` 库使用的基本概念和术语。理解这些概念将帮助您更有效地使用该库,并轻松创建复杂的表单。
您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。