TanStack Form 功能的核心是验证概念。TanStack Form 使验证高度可定制:
这取决于您![tanstackField] 指令接受一些回调作为属性,如 onChange 或 onBlur。这些回调会传递字段的当前值以及 fieldAPI 对象,以便您可以执行验证。如果您发现验证错误,只需将错误消息作为字符串返回,它将在 field.api.state.meta.errors 中可用。
这是一个示例:
@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">年龄:</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 ? '您必须年满 13 岁才能创建账户' : 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">年龄:</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 ? '您必须年满 13 岁才能创建账户' : undefined
// ...
}
在上面的示例中,验证在每次按键时执行(onChange)。如果我们希望在字段失焦时执行验证,我们会像这样更改上面的代码:
@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">年龄:</label>
<!-- 我们始终需要实现 onChange,以便 TanStack Form 接收更改 -->
<!-- 监听字段上的 onBlur 事件 -->
<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 ? '您必须年满 13 岁才能创建账户' : 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">年龄:</label>
<!-- 我们始终需要实现 onChange,以便 TanStack Form 接收更改 -->
<!-- 监听字段上的 onBlur 事件 -->
<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 ? '您必须年满 13 岁才能创建账户' : 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">年龄:</label>
<!-- 我们始终需要实现 onChange,以便 TanStack Form 接收更改 -->
<!-- 监听字段上的 onBlur 事件 -->
<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 ? '您必须年满 13 岁才能创建账户' : undefined
minimumAgeValidator: FieldValidateFn<any, any, any, any, number> = ({
value,
}) => (value < 0 ? '无效值' : 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">年龄:</label>
<!-- 我们始终需要实现 onChange,以便 TanStack Form 接收更改 -->
<!-- 监听字段上的 onBlur 事件 -->
<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 ? '您必须年满 13 岁才能创建账户' : undefined
minimumAgeValidator: FieldValidateFn<any, any, any, any, number> = ({
value,
}) => (value < 0 ? '无效值' : undefined)
// ...
}
在上面的示例中,我们在不同时间对同一字段验证不同的内容(在每次按键时和字段失焦时)。由于 field.state.meta.errors 是一个数组,因此会显示给定时间的所有相关错误。您还可以使用 field.state.meta.errorMap 根据验证执行的_时间_(onChange、onBlur 等)获取错误。有关显示错误的更多信息请参见下文。
一旦您设置了验证,您可以将错误从数组映射到 UI 中显示:
@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 ? '您必须年满 13 岁才能创建账户' : 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 ? '您必须年满 13 岁才能创建账户' : undefined
// ...
}
或使用 errorMap 属性访问您要查找的特定错误:
@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 ? '您必须年满 13 岁才能创建账户' : 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 ? '您必须年满 13 岁才能创建账户' : undefined
// ...
}
值得一提的是,我们��� errors 数组和 errorMap 与验证器返回的类型匹配。这意味着:
@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">用户年龄不够</em>
}
</ng-container>
`,
})
export class AppComponent {
ageValidator: FieldValidateFn<any, any, any, any, number> = ({ value }) =>
value < 13 ? '您必须年满 13 岁才能创建账户' : 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">用户年龄不够</em>
}
</ng-container>
`,
})
export class AppComponent {
ageValidator: FieldValidateFn<any, any, any, any, number> = ({ value }) =>
value < 13 ? '您必须年满 13 岁才能创建账户' : undefined
// ...
}
如上所示,每个 [tanstackField] 通过 onChange、onBlur 等回调接受自己的验证规则。也可以通过向 injectForm() 函数传递类似的回调来在表单级别(而不是逐个字段)定义验证规则。
示例:
@Component({
selector: 'app-root',
standalone: true,
imports: [TanStackField],
template: `
<div>
<ng-container [tanstackField]="form" name="age" #age="field">
<!-- ... -->
@if (formErrorMap().onChange) {
<div>
<em
>表单出现错误:{{ formErrorMap().onChange }}</em
>
</div>
}
<!-- ... -->
</ng-container>
</div>
`,
})
export class AppComponent {
form = injectForm({
defaultValues: {
age: 0,
},
onSubmit({ value }) {
console.log(value)
},
validators: {
// 以与向字段添加验证器相同的方式向表单添加验证器
onChange({ value }) {
if (value.age < 13) {
return '必须年满 13 岁才能注册'
}
return undefined
},
},
})
// 订阅表单的错误映射,以便对其的更新会渲染
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
>表单出现错误:{{ formErrorMap().onChange }}</em
>
</div>
}
<!-- ... -->
</ng-container>
</div>
`,
})
export class AppComponent {
form = injectForm({
defaultValues: {
age: 0,
},
onSubmit({ value }) {
console.log(value)
},
validators: {
// 以与向字段添加验证器相同的方式向表单添加验证器
onChange({ value }) {
if (value.age < 13) {
return '必须年满 13 岁才能注册'
}
return undefined
},
},
})
// 订阅表单的错误映射,以便对其的更新会渲染
formErrorMap = injectStore(this.form, (state) => state.errorMap)
}
您可以从表单的验证器在字段上设置错误。一个常见的用例是通过在表单的 onSubmitAsync 验证器中调用单个 API 端点来在提交时验证所有字段。
@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">年龄:</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">提交</button>
</form>
`,
})
export class AppComponent {
form = injectForm({
defaultValues: {
age: 0,
socials: [],
details: {
email: '',
},
},
validators: {
onSubmitAsync: async ({ value }) => {
// 在服务器上验证值
const hasErrors = await verifyDataOnServer(value)
if (hasErrors) {
return {
form: '无效数据', // `form` 键是可选的
fields: {
age: '必须年满 13 岁才能注册',
// 使用字段名称在嵌套字段上设置错误
'socials[0].url': '提供的 URL 不存在',
'details.email': '需要电子邮件',
},
};
}
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">年龄:</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">提交</button>
</form>
`,
})
export class AppComponent {
form = injectForm({
defaultValues: {
age: 0,
socials: [],
details: {
email: '',
},
},
validators: {
onSubmitAsync: async ({ value }) => {
// 在服务器上验证值
const hasErrors = await verifyDataOnServer(value)
if (hasErrors) {
return {
form: '无效数据', // `form` 键是可选的
fields: {
age: '必须年满 13 岁才能注册',
// 使用字段名称在嵌套字段上设置错误
'socials[0].url': '提供的 URL 不存在',
'details.email': '需要电子邮件',
},
};
}
return null;
},
},
});
handleSubmit(event: SubmitEvent) {
event.preventDefault();
event.stopPropagation();
this.form.handleSubmit();
}
}
值得一提的是,如果您有一个返回错误的表单验证函数,该错误可能会被字段特定的验证覆盖。
这意味着:
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; }即使表单级别验证返回 'Too young!' 错误,也只会显示 'Must be odd!。
虽然我们认为大多数验证都是同步的,但在许多情况下,网络调用或其他异步操作对验证很有用。
为此,我们有专门的 onChangeAsync、onBlurAsync 和其他可用于验证的方法:
@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">姓氏:</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 ? '您必须年满 13 岁才能创建账户' : 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">姓氏:</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 ? '您必须年满 13 岁才能创建账户' : undefined
}
// ...
}
同步和异步验证可以共存。例如,可以在同一字段上定义 onBlur 和 onBlurAsync:
@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">姓氏:</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 ? '您必须至少年满 13 岁' : undefined
ensureOlderAge: FieldValidateAsyncFn<any, string, number> = async ({
value,
}) => {
const currentAge = await fetchCurrentAgeOnProfile()
return value < currentAge ? '您只能增加年龄' : 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">姓氏:</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 ? '您必须至少年满 13 岁' : undefined
ensureOlderAge: FieldValidateAsyncFn<any, string, number> = async ({
value,
}) => {
const currentAge = await fetchCurrentAgeOnProfile()
return value < currentAge ? '您只能增加年龄' : undefined
}
// ...
}
同步验证方法(onBlur)首先运行,异步方法(onBlurAsync)仅在同步方法(onBlur)成功时运行。要更改此行为,请将 asyncAlways 选项设置为 true,异步方法将无论同步方法的结果如何都会运行。
虽然在对数据库进行验证时异步调用是正确的方法,但在每次按键时运行网络请求是对数据库进行 DDOS 攻击的好方法。
相反,我们通过添加单个属性来启用对 async 调用进行防抖的简单方法:
<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>
这将以 500ms 延迟对每个异步调用进行防抖。您甚至可以在每个验证属性上覆盖此属性:
<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>
这将每 1500ms 运行一次 onChangeAsync,而 onBlurAsync 将每 500ms 运行一次。
虽然函数为您的验证提供了更多的灵活性和自定义,但它们可能有点冗长。为了帮助解决这个问题,有一些库提供基于模式的验证,使简写和类型严格的验证变得更加容易。您还可以为整个表单定义单个模式并将其传递到表单级别,错误将自动传播到字段。
TanStack Form 原生支持所有遵循 Standard Schema 规范 的库,最著名的是:
注意: 确保使用模式库的最新版本,因为较旧的版本可能尚不支持 Standard Schema。
验证不会为您提供转换后的值。有关更多信息,请参阅 提交处理。
要使用这些库中的模式,您可以将它们传递给 validators 属性,就像使用自定义函数一样:
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, '您必须年满 13 岁才能创建账户'),
}"
#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, '您必须年满 13 岁才能创建账户'),
}"
#age="field"
>
<!-- ... -->
</ng-container>
`,
})
export class AppComponent {
form = injectForm({
// ...
})
z = z
// ...
}
表单和字段级别的异步验证也受支持:
@Component({
selector: 'app-root',
standalone: true,
imports: [TanStackField],
template: `
<ng-container
[tanstackField]="form"
name="age"
[validators]="{
onChange: z.number().gte(13, '您必须年满 13 岁才能创建账户'),
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: '您只能增加年龄',
},
)
// ...
}
@Component({
selector: 'app-root',
standalone: true,
imports: [TanStackField],
template: `
<ng-container
[tanstackField]="form"
name="age"
[validators]="{
onChange: z.number().gte(13, '您必须年满 13 岁才能创建账户'),
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: '您只能增加年龄',
},
)
// ...
}
如果您需要对 Standard Schema 验证进行更多控制,您可以将 Standard Schema 与回调函数结合使用,如下所示:
@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, '您必须年满 13 岁才能创建账户'),
)
if (errors) return errors
// 继续您的验证
}
// ...
}
@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, '您必须年满 13 岁才能创建账户'),
)
if (errors) return errors
// 继续您的验证
}
// ...
}
当提交表单时,onChange、onBlur 等回调也会运行,如果表单无效,提交将被阻止。
表单状态对象有一个 canSubmit 标志,当任何字段无效且表单已被触摸时,该标志为 false(canSubmit 在表单被触摸之前为 true,即使某些字段根据其 onChange/onBlur 属性在"技术上"无效)。
您可以通过 injectStore 订阅它并使用该值,例如,在表单无效时禁用提交按钮(在实践中,禁用的按钮不可访问,请改用 aria-disabled)。
@Component({
selector: 'app-root',
standalone: true,
imports: [TanStackField],
template: `
<!-- ... -->
<button type="submit" [disabled]="!canSubmit()">
{{ isSubmitting() ? '...' : '提交' }}
</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() ? '...' : '提交' }}
</button>
`,
})
export class AppComponent {
canSubmit = injectStore(this.form, (state) => state.canSubmit)
isSubmitting = injectStore(this.form, (state) => state.isSubmitting)
// ...
}