首页
/ Angular Signal Forms:基于信号的表单体系总览与源码级实践指南

Angular Signal Forms:基于信号的表单体系总览与源码级实践指南

2026-09-06 17:40:02作者:田桥桑Industrious

Signal Forms(信号表单)是 Angular 官方 @angular/forms 包内置的一套表单解决方案,它以 Angular Signals 为响应式基础,通过 form() 创建类型安全的字段树(FieldTree),用 [formField] 指令实现输入控件与数据模型之间的自动双向绑定。本文基于仓库文档 adev/src/content/guide/forms/signals/overview.md 展开,并结合 packages/forms/signals/ 目录下的真实源码,带你完整掌握 Signal Forms 的适用场景、接入方式、核心 API 以及底层实现机制。

什么是 Signal Forms

在 Web 应用里构建表单,通常要同时处理多个相互纠缠的问题:追踪字段值、校验用户输入、处理错误状态、以及让 UI 与数据模型保持同步。如果把这些关注点分开管理,就会产生大量样板代码和复杂度。

Signal Forms 建立在信号的响应式基础之上,通过三个核心能力应对这些挑战:

  • 自动状态同步——表单数据模型与绑定的表单字段之间自动同步,无需手写 setValue/valueChanges 订阅;
  • 类型安全——表单模式(schema)与 UI 控件到数据模型之间的绑定都具备完整的类型安全;
  • 集中式校验——所有校验规则集中定义在一个校验模式函数中,而不是散落在各个控件里。

在仓库中,这套 API 以 @angular/forms/signals 子模块的形式发布,源码位于 packages/forms/signals/。该目录下的 PACKAGE.md 明确说明:它是构建在 Angular Signals 之上的信号版表单 API,是模板驱动表单(template-driven)与响应式表单(reactive forms)之外的替代方案,并且与现有 @angular/forms API 保持互操作

何时使用 Signal Forms

官方文档给出的选型建议是:

前置条件

Signal Forms 要求 Angular v21 或更高版本

就当前仓库而言,根目录 package.json 中的版本号为 22.2.0-next.4(主分支)。值得注意的是,源码中对部分新 API(如 FORM_FIELD 注入令牌、表单控件契约接口)标注了 @publicApi 22.0,说明这些契约是在 22.0 中正式对外暴露的。如果你的项目尚停留在 v21,核心用法(form() + [formField])可用,但涉及自定义控件契约等 22.0 新 API 时应注意版本差异。

接入与导入

Signal Forms 已经包含在 @angular/forms 包内,无需额外安装依赖。从 @angular/forms/signals 导入所需函数和指令即可:

import {form, FormField, required, email} from '@angular/forms/signals';

FormField 指令必须导入到任何需要将表单字段绑定到 HTML 输入控件的组件中:

@Component({
  // ...
  imports: [FormField],
})

公共 API 入口一览

public_api.ts 可以看到,该子模块对外导出的内容包括:

导出模块 对应源码位置 职责
form() 等断言/结构函数 assertions.tsstructure.ts 构建字段树、类型断言
校验规则(requiredemailminmaxpattern 等) rules/ 同步/异步校验、禁用、隐藏、元数据规则
debounce() debounce.ts 校验防抖
transformedValue() transformed_value.ts 值转换
FormField / FormRoot 指令 form_field.tsform_root.ts 双向绑定与表单根节点
WebMCP 支持 webmcp/ 表单与 WebMCP 的集成

核心使用流程

下面是最小可用路径的完整示例(与文档 Signal Forms essentials 中给出的模式一致,完整可运行示例可参考该文档以及仓库中的示例工程目录 adev/src/content/examples/signal-forms/)。

1. 用 signal() 创建表单模型

每个表单都从一个保存数据模型的信号开始:

interface LoginData {
  email: string;
  password: string;
}

const loginModel = signal<LoginData>({
  email: '',
  password: '',
});

2. 用 form() 生成 FieldTree

把表单模型传入 form(),得到一个字段树(FieldTree)——一个与模型形状一致的对象结构,可以按点号访问各字段。根表单对象与它的嵌套属性都是 FieldTree 节点:

const loginForm = form(loginModel);

loginForm;        // 是一个 FieldTree
loginForm.email; // 也是一个 FieldTree

3. 用 [formField] 指令绑定输入控件

[formField] 在 HTML 输入控件与字段之间建立双向绑定:

<input type="email" [formField]="loginForm.email" />
<input type="password" [formField]="loginForm.password" />

用户输入会自动更新表单;该指令还会在适当时候同步 requireddisabledreadonly 等字段状态。[formField] 兼容所有标准 HTML 输入类型,常见模式包括:

  • 文本/邮箱输入<input type="text" [formField]="form.name" /><input type="email" [formField]="form.email" />
  • 数字输入<input type="number" [formField]="form.age" />,自动在字符串与数字之间转换
  • 日期/时间<input type="date"> 存储 YYYY-MM-DD 字符串,<input type="time"> 使用 HH:mm 格式,需要 Date 对象时可 new Date(form.eventDate().value()) 转换
  • 多行文本<textarea [formField]="form.message" rows="4"></textarea>
  • 复选框:绑定布尔值,多选项时为每项创建独立布尔 formField
  • 单选按钮:同一组 radio 使用相同 [formField],Signal Forms 会自动为它们绑定相同的 name 属性,选中后字段值为对应 radio 的 value 属性
  • 下拉选择<select> 支持静态选项与 @for 动态选项

4. 用 FieldTree 状态信号读取状态

调用任意节点即可取得其状态对象,其中包含值、校验状态与交互状态的响应式信号:

loginForm();        // 整个表单的状态
loginForm.email();  // email 字段的状态

每个节点(包括根节点)都暴露同一组状态信号,API 在树的每一级完全一致:

状态信号 含义
valid() 该节点通过所有校验规则
invalid() 存在校验错误
pending() 异步校验正在进行中
touched() 用户已聚焦并离开该字段(或任一子字段)
dirty() 值已被用户修改
disabled() 节点被禁用
readonly() 节点为只读
errors() 校验错误数组,每项含 kindmessage 属性
<!-- 随用户输入自动更新的值显示 -->
<p>Form value: {{ loginForm().value() | json }}</p>
<p>Email: {{ loginForm.email().value() }}</p>

5. 用 value.set() 编程式更新

任意节点都可以通过 value.set() 更新值,字段树与底层模型信号会同步更新:

loginForm.email().value.set('alice@wonderland.com');

console.log(loginModel().email); // 'alice@wonderland.com'

校验:把规则集中在 schema 函数里

将模式函数作为 form() 的第二个参数传入,即可声明校验规则。模式函数接收一个 schema path 参数,提供指向各字段的路径:

const loginForm = form(loginModel, (schemaPath) => {
  debounce(schemaPath.email, 500);
  required(schemaPath.email);
  email(schemaPath.email);
});

常用内置校验器:required()email()min()/max()minLength()/maxLength()pattern()。还可通过第二个参数自定义错误信息:

required(schemaPath.email, {message: 'Email is required'});
email(schemaPath.email, {message: 'Please enter a valid email address'});

更多规则细节(自定义规则、异步校验、交叉字段逻辑等)见 Validation 指南Schemas 指南

源码解析:FormField 指令如何工作

FormField 指令是双向绑定的核心,定义在 form_field.ts 中:

@Directive({
  selector: '[formField]',
  exportAs: 'formField',
  providers: [
    {provide: FORM_FIELD, useExisting: FormField},
    {provide: NgControl, useFactory: () => inject(FormField).interopNgControl},
    {provide: FORM_CONTROL_INTEGRATION, useFactory: () => inject(FORM_FIELD, {self: true})},
  ],
})
export class FormField<T> { ... }

从源码注释可以确认,该指令承担四项职责:

  1. 在字段状态的 value 与 UI 控件的值之间双向绑定
  2. 把字段状态中与表单相关的其他状态(disabledrequired 等)绑定到 UI 控件上;
  3. 将控件上的相关事件(例如 blur 时标记 touched)中继回字段状态;
  4. 提供一个实现 NgControl 子集接口的“假 NgControl”(通过 interopNgControl),用于与为响应式表单设计的控件互操作——源码注释明确建议:为信号表单编写的新控件不应依赖这一互操作层。

指令还支持三类宿主控件:原生 HTML input/textarea、实现 FormValueControlFormCheckboxControl 契约的信号表单自定义控件、以及提供 ControlValueAccessor 的组件(仅用于向后兼容响应式表单,官方建议优先使用前两者)。指令还会通过 InputValidityMonitor 与字段节点(field/node.ts)协作管理绑定生命周期,相关行为可在 test/web/form_field.spec.ts 等测试中验证。

自定义控件契约

control.ts 定义了三个公共契约(@publicApi 22.0):

  • FormUiControl<TValue>L22):所有表单控件共享的基础契约,声明了大量可选的 InputSignal(如 errorsdisabledinvalidtoucheddirtyrequiredmin/maxpattern 等)以及 focus()/reset() 方法。控件一旦实现这些输入,Field 指令就会自动把绑定字段的状态同步进来;
  • FormValueControl<TValue>L160):编辑任意类型值的控件契约,必须提供 value: ModelSignal<TValue>
  • FormCheckboxControlL192):编辑布尔型复选框的契约,必须提供 checked: ModelSignal<boolean>

完整的自定义控件编写方法见 Custom controls 指南

校验规则的实现细节

required() 为例,其实现位于 required.ts。除了文档中提到的 message 选项外,源码显示它实际支持更完整的配置项:

  • message:面向用户的错误文案;
  • error:自定义校验错误(或接收 FieldContext 返回错误的函数),用于替代默认的 ValidationError.required()
  • when:接收 FieldContext 返回布尔值的函数,用于条件必填——required() 会先通过 metadata(path, ...)when 的计算结果 memoize 为元数据,只有该值为真且当前值为空时才产生错误。

rules/validation/ 目录下的完整内置规则包括:email.tsmax.tsmax_date.tsmax_length.tsmin.tsmin_date.tsmin_length.tspattern.tsrequired.ts,以及用于编写自定义规则的基础设施 validate.ts(同步)、validate_async.ts(异步)与 validate_http.ts(HTTP 校验)。规则对应的单元/端到端测试位于 test/node/api/validators/ 目录。

当前限制

阅读文档时应注意以下仓库中明确标注的限制:

  • PACKAGE.md 列出了暂不支持的特性:校验防抖(debouncing validation)、动态对象(dynamic objects)、元组(tuples);
  • 多值下拉 <select multiple> 目前不受 [formField] 指令支持(见 essentials 指南 的说明)。

在评估是否将 Signal Forms 引入既有生产应用时,应将这些边界纳入考量。

学习路径:配套指南索引

概述文档为深入阅读规划了完整路线,以下是仓库中的对应文档(均位于 adev/src/content/guide/forms/signals/):

主题 文档
Signal Forms 入门速览 adev/src/content/introduction/essentials/signal-forms.md
表单模型(创建与管理数据) models.md
设计表单模型 designing-your-form-model.md
字段状态管理 field-state-management.md
校验 validation.md
自定义控件 custom-controls.md
与其他表单体系对比 comparison.md
迁移指南 migration.md
Schema 详解 schemas.md
测试 testing.md
异步操作 / 表单提交 / 跨字段逻辑 async-operations.mdform-submission.mdcross-field-logic.md

掌握本篇内容后,你将能够:判断 Signal Forms 是否适合你的应用、完成 @angular/forms/signals 的接入配置、用 signal() + form() + [formField] 搭建类型安全的双向绑定表单、通过 schema 函数集中声明校验规则,并理解 FormField 指令、控件契约与校验规则在源码层面的工作机制。

登录后查看全文
热门项目推荐
相关项目推荐