、FormRoot 与提交状态管理指南)
Angular Signal Forms 表单提交实战submit()、FormRoot 与提交状态管理指南【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angularSignal Formsangular/forms/signals的submit()函数为表单提交流程提供了一套受控的完整生命周期标记字段为已交互、执行验证门禁、运行异步action、把服务端错误路由回目标字段。本文基于 Angular 仓库中的官方文档 form-submission 指南 与 Signal Forms 源码完整讲解如何用FormRoot指令接入提交流程、用submitting()防重复提交、用onInvalid处理无效提交、用ignoreValidators控制验证门禁并直接调用submit()实现手动提交与副作用编排。提交流程submit()的四个阶段当用户提交表单时应用通常要同时处理多件事暴露验证错误、防止重复提交、把数据发送到服务器。Signal Forms 用submit()把这些关注点收敛到一条固定序列中标记交互字段为 touched— 仅在被触碰后才显示错误的字段会立即展示验证错误hidden、disabled、readonly 字段会被跳过。验证门禁检查— 若任何验证规则失败提交中止action函数不会执行。运行 action—action函数以表单当前值执行期间submitting()返回true。处理结果— 若 action 返回错误错误会被路由到目标字段若无返回视为提交成功。submit()返回Promisebooleanaction 无错误地完成时为true验证失败或 action 返回错误时为false。源码视角提交流程的实现在 submit() 实现 中可以完整看到这条序列const node untracked(form) as FieldStateunknown as FieldNode; // 并发保护同一表单或其父表单正在提交时立即返回 false if (untracked(node.submitState.submitting)) { return false; } // ... node.markAsTouched(); const onInvalid options?.onInvalid as ...; const shouldRun shouldRunAction(node, options?.ignoreValidators); try { if (shouldRun) { node.submitState.selfSubmitting.set(true); const errors await untracked(() action?.(field, detail)); errors setSubmissionErrors(node, errors); return !errors || (isArray(errors) errors.length 0); } else { untracked(() onInvalid?.(field, detail)); } return false; } finally { node.submitState.selfSubmitting.set(false); }几个值得注意的实现细节markAsTouched()先于门禁检查因此验证错误在action或onInvalid执行时已经可见action的返回值决定是否成功返回null/undefined/空数组为成功返回非空错误对象或错误数组则失败并发保护发生在函数入口——从源码结构看任何同一表单或其父表单正在提交的重入调用都会被短路为false若既没有通过submit()传入 action也没有在创建表单时配置submission.action会抛出MISSING_SUBMIT_ACTION运行时错误提示 Specify the action when creating the form, or as an additional argument tosubmit()。action与onInvalid的函数签名定义在 FormSubmitOptions 接口 中两者都接收(field, detail)参数detail包含root表单根字段树与submitted本次提交的字段树在提交子表单时区分二者尤为有用。用 FormRoot 指令接入提交使用submit()最常见的方式是通过FormRoot指令绑定到form元素。该指令自动处理三件事见 form_root.ts设置novalidate— 禁用浏览器内置验证改由 Signal Forms 管理验证。源码中通过 host 绑定novalidate: 自动完成无需手动添加preventDefault— 阻止浏览器在表单提交时发起导航调用submit()— 用户提交表单时触发提交流程。指令实现非常简洁Directive({ selector: form[formRoot], host: { novalidate: , (submit): onSubmit($event), }, }) export class FormRootT { readonly fieldTree input.requiredFieldTreeT({alias: formRoot}); protected onSubmit(event: Event): void { event.preventDefault(); untracked(() { const fieldTree this.fieldTree(); const node fieldTree() as FieldStateunknown as FieldNode; // 仅当表单定义了 submission 选项时才触发提交 if (node.structure.fieldManager.submitOptions) { submit(fieldTree); } }); } }从实现可以看到FormRoot只负责触发提交提交后做什么仍由你在form()中配置。需要三件事把表单绑定到FormRoot指令给form()函数传入submission选项在submission选项内定义处理数据的action函数。import {Component, signal} from angular/core; import {form, FormField, FormRoot, required} from angular/forms/signals; Component({ selector: app-contact, imports: [FormField, FormRoot], template: form [formRoot]contactForm label Name input [formField]contactForm.name / /label label Email input typeemail [formField]contactForm.email / /label button typesubmitSend/button /form , }) export class Contact { contactModel signal({ name: , email: , }); contactForm form( this.contactModel, (schemaPath) { required(schemaPath.name); required(schemaPath.email); }, { submission: { action: async (field) { const result await saveContact(field().value()); if (result.ok) return; return {kind: serverError, message: Failed to submit form}; }, }, }, ); }action函数只会在没有验证规则失败时才执行默认情况下pending 状态的异步验证器不阻塞提交见下文ignoreValidators一节。验证通过之后action 本身仍可能因网络错误、重复数据等原因失败——此时通过返回错误来暴露失败而表示成功只需返回null或undefined或使用空return。用 submitting() 展示提交状态submitting()信号在action运行期间返回true可用于显示加载指示器或禁用提交按钮以阻止重复提交。模板示例button typesubmit [disabled]contactForm().submitting() if (contactForm().submitting()) { Sending... } else { Send } /buttonaction 成功或返回错误后submitting()会自动复位为false——这对应源码中finally { node.submitState.selfSubmitting.set(false); }的清理逻辑即使 action 抛出异常也不会卡死在提交中。源码视角submitting 信号的构成FieldSubmitState 类维护两层状态export class FieldSubmitState { /** 该字段是否被直接提交而非父字段带动且仍在提交中 */ readonly selfSubmitting signalboolean(false); /** 与提交相关的错误随表单结构变化自动重置 */ readonly submissionErrors: WritableSignalreadonly ValidationError.WithFieldTree[]; /** 该表单是否正在提交中自己被提交或某个父字段被提交 */ readonly submitting: Signalboolean computed(() { return this.selfSubmitting() || (this.node.structure.parent?.submitting() ?? false); }); }这里有一个关键设计submitting()是沿父链聚合的 computed 信号。当根表单提交时所有子字段的submitting()都为true这也正是同一表单或其任一父表单提交中时后续submit()调用立即返回false这一并发保护规则的来源。管理提交错误服务端错误当action与服务器通信时服务器可能返回需要显示在特定字段上的错误。从action中返回这些错误即可将其路由到目标字段。错误对象遵循 ValidationError 类型族kind标识错误类型自由字符串message为人读消息fieldTree指定错误归属的字段。归属到被提交字段的错误默认情况下从action返回的错误会归属到被提交的字段即传给submit()的字段树action: async (field) { const result await saveContact(field().value()); if (result.ok) return; return {kind: serverError, message: Failed to submit form}; };归属到特定字段的错误要把错误路由到特定字段加入指向该字段的fieldTree属性action: async (field) { const result await saveContact(field().value()); if (result.ok) return; return {kind: taken, message: result.message, fieldTree: field.email}; };多个错误要向多个字段报告错误返回数组action: async (field) { const result await registerUser(field().value()); if (result.ok) return; return result.errors.map((err: {field: string; message: string}) ({ kind: serverError, message: err.message, fieldTree: field[err.field as keyof typeof field], })); };从源码看submit() 内部 的setSubmissionErrors会遍历返回的错误没有fieldTree的错误通过addDefaultField补上默认归属被提交的字段然后按字段分组写入各字段的submissionErrors信号。提交错误的自动清除当用户编辑对应字段时提交错误会自动清除——例如 action 在 email 字段上返回的错误会随用户修改 email 值而消失。这与验证错误不同验证错误是响应式重算的每次变更都会重新运行验证规则并可能产生相同错误提交错误是一次性的服务端结果——清除后除非再次提交否则不会重现。提示提交错误与验证错误一同出现在字段的errors()信号中可在模板中统一渲染。用 onInvalid 处理无效提交验证失败时action函数不会运行。如果需要响应失败的提交尝试——比如滚动到第一个错误、弹出 toast、聚焦到无效字段——使用onInvalid回调contactForm form( this.contactModel, (schemaPath) { required(schemaPath.name); required(schemaPath.email); }, { submission: { action: async (field) { await saveContact(field().value()); }, onInvalid: (field) { const firstError field().errorSummary()[0]; firstError?.fieldTree().focusBoundControl(); }, }, }, );onInvalid回调接收与action相同的(field, detail)参数。它会在所有交互字段被标记为 touched 之后执行因此回调运行时验证错误已在 UI 中可见。用 ignoreValidators 控制验证门禁默认情况下submit()忽略 pending 状态的验证器只要没有验证器失败即使某些异步验证器仍在进行中action 也会运行。ignoreValidators选项让你控制这一行为取值行为pending没有验证器失败即可提交即使部分验证器仍在 pending默认值none仅当所有验证器通过时才提交——pending 验证器会阻塞提交all无视验证状态始终提交contactForm form( this.contactModel, (schemaPath) { required(schemaPath.name); required(schemaPath.email); }, { submission: { action: async (field) { await saveContact(field().value()); }, ignoreValidators: none, }, }, );适用场景当表单带有异步验证器如检查用户名是否可用、且需要所有验证完成后再提交时使用none对于草稿保存这类无论验证状态都要持久化数据的场景使用all。三种取值的语义在 FormSubmitOptions 类型注释 中有权威定义运行时门禁逻辑由源码中的shouldRunAction(node, options?.ignoreValidators)判断。手动调用 submit()FormRoot指令是触发提交最常见的方式但你也可以直接调用submit()——适用于多步向导、自动保存、或从form元素外部触发提交等场景import {Component, signal} from angular/core; import {form, FormField, required, submit} from angular/forms/signals; Component({ selector: app-contact, imports: [FormField], template: label Name input [formField]contactForm.name / /label label Email input typeemail [formField]contactForm.email / /label button (click)onSave()Save/button , }) export class Contact { contactModel signal({ name: , email: , }); contactForm form(this.contactModel, (schemaPath) { required(schemaPath.name); required(schemaPath.email); }); async onSave() { // 直接调用 submit() 时action 作为第二个参数传入 // 而不是配置在 FormOptions 中 const success await submit(this.contactForm, async (field) { const result await saveContact(field().value()); if (result.ok) return; return {kind: serverError, message: Failed to save}; }); if (success) { // 处理成功——导航、显示确认信息等 } } }从源码看submit()的重载同时接受 options 对象和裸 action 函数两种形式typeof options function ? {action: options} : (options ?? node.structure.fieldManager.submitOptions)。也就是说如果表单创建时配置了submission选项直接调用submit(form)不传参数也可复用已配置的 action。处理副作用submit()返回Promiseboolean——action 无错误完成时为true验证失败或 action 返回错误时为false。利用它触发导航或通知等副作用async onSave() { const success await submit(this.contactForm, async (field) { await saveContact(field().value()); }); if (success) { await this.router.navigate([/confirmation]); } }当副作用需要 action 产生的数据如服务端生成的 ID时把副作用放进 action 内部处理async onSave() { await submit(this.contactForm, async (field) { const contact await createContact(field().value()); await this.router.navigate([/confirmation, contact.id]); }); }使用FormRoot时同理副作用也应写在action内部因为FormRoot内部调用的是submit()拿不到 action 的中间产物submission: { action: async (field) { const result await saveContact(field().value()); if (result.ok) { await this.router.navigate([/confirmation]); return; } return {kind: serverError, message: Failed to submit form}; }, }并发提交保护当一次提交正在进行中时针对同一表单或其任一父表单的后续submit()调用会立即返回false且不执行 action。这防止了用户快速多次触发提交导致的重复请求与副作用——其实现就是submit()入口处的if (untracked(node.submitState.submitting)) return false;短路检查与上文FieldSubmitState中沿父链聚合的submitting计算信号共同构成防护。小结Signal Forms 的提交流程把标记 touched → 验证门禁 → 运行 action → 错误路由收敛为一条可预测的序列并提供了三个层次的扩展点配置层form()的submission选项action、onInvalid、ignoreValidators决定提交行为模板层FormRoot指令接管form事件submitting()驱动禁用按钮与加载态命令式层直接submit()支持向导、自动保存等脱离form元素的场景返回值驱动导航等副作用。核心实现可参考 submit() 函数、FormSubmitOptions 类型、FieldSubmitState、FormRoot 指令以及测试 submit.spec.ts。相关的其他指南包括 验证、字段状态管理 与 表单逻辑。【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考