Angular 21 中的 Signal 表单

发布: (2026年2月12日 GMT+8 16:02)
8 分钟阅读
原文: Dev.to

I’m happy to translate the article for you, but I’ll need the text of the article itself. Could you please paste the content you’d like translated (excluding the source line you’ve already provided)? Once I have the full text, I’ll translate it into Simplified Chinese while preserving all formatting, markdown, and code blocks.

Angular 21 Signal Forms – 全新的思维模型

多年来,Angular 表单只意味着一种东西:FormGroupFormControlvalueChanges,以及我们几乎机械式学习导航的 AbstractControl 树。
如果你已经使用 Angular 很多年,可能对它们非常熟悉。

但 Angular 21 引入的并不仅仅是一个新 API——它是一种 全新的思维模型

Signal Forms 并不是 Reactive Forms 的演进。一旦在非平凡场景中使用它们,你会注意到 表单不再像框架特性那样

从“登录表单”到真实的结账表单

与其使用经典的“登录表单”示例,不如构建更贴近生产实际的东西:一个包含嵌套对象、条件支付逻辑以及跨字段校验的结账表单。
我们从唯一真正重要的东西开始——模型

import { signal } from '@angular/core';

export interface CheckoutModel {
  customer: {
    firstName: string;
    lastName: string;
    email: string;
  };
  shipping: {
    street: string;
    city: string;
    zip: string;
    country: string;
  };
  billingSameAsShipping: boolean;
  billing: {
    street: string;
    city: string;
    zip: string;
    country: string;
  };
  payment: {
    method: 'card' | 'paypal';
    cardNumber: string;
    expiry: string;
    cvv: string;
  };
  acceptTerms: boolean;
}

checkoutModel = signal({
  customer: {
    firstName: '',
    lastName: '',
    email: ''
  },
  shipping: {
    street: '',
    city: '',
    zip: '',
    country: ''
  },
  billingSameAsShipping: true,
  billing: {
    street: '',
    city: '',
    zip: '',
    country: ''
  },
  payment: {
    method: 'card',
    cardNumber: '',
    expiry: '',
    cvv: ''
  },
  acceptTerms: false
});

为什么这样很棒

  • 表单不是控制树——它是一个类型化信号
  • 它是唯一的真实来源
  • 它可以自然地与 computedeffect 以及无区块(zoneless)Angular 集成。
  • 没有二级抽象层

Signal Forms 会生成一个映射你模型的 FieldTree

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

checkoutForm = form(this.checkoutModel, (path) => {
  required(path.customer.firstName);
  required(path.customer.lastName);
  required(path.customer.email);
  email(path.customer.email);

  required(path.shipping.street);
  required(path.shipping.city);
  required(path.shipping.zip);
  required(path.shipping.country);

  required(path.acceptTerms);
});

模式(schema)函数极其重要——它迫使验证紧贴数据结构,而不是 UI、组件或抽象类。验证是基于模型结构声明的,为你提供了架构上的清晰度。

模板 – 不使用 formGroupNameformControlName

  • 你不会 “获取” 嵌套的组。
  • 你不会遍历 AbstractControl 树。
  • 结构已经存在,因为它镜像了你的 signal 模型。

这感觉更像普通的 TypeScript,而不是框架配置。

动态支付方式

  Card
  PayPal

@if (checkoutForm.payment.method().value() === 'card') {
  
  
  
}
  • 没有订阅。
  • 没有 valueChanges
  • 没有 async pipe。

UI 会响应,因为 Signals 会响应。这是 Angular 自从引入 Signals 以来一直在推进的架构一致性。

跨字段验证变得轻而易举

Reactive Forms 中最痛苦的部分之一一直是跨字段验证——你往往需要编写检查兄弟控件并返回错误映射的表单级验证器。

使用 Signal Forms,模型本身已经是一个 signal,所以你只需要读取它。

validate(path.payment.cardNumber, ({ value }) => {
  const model = this.checkoutModel();

  if (model.payment.method !== 'card') {
    return null;
  }

  return value().length  {
  const { customer, shipping } = this.checkoutModel();

  return {
    fullName: `${customer.firstName} ${customer.lastName}`,
    destination: `${shipping.city}, ${shipping.country}`
  };
});
  • 无需监听表单变化。
  • valueChanges
  • 模型变化 → 计算属性重新计算 → 模板更新。

响应式在你的整个应用中保持一致。

Effects – 处理派生状态(例如,“账单地址与收货地址相同”)

常见需求:账单地址与收货地址相同。在 Reactive Forms 中,你需要手动 patch 值,并且要小心避免循环更新。使用 Signals 则变得非常简单:

import { effect } from '@angular/core';

constructor() {
  effect(() => {
    const model = this.checkoutModel();

    if (model.billingSameAsShipping) {
      this.checkoutModel.update(current => ({
        ...current,
        billing: { ...current.shipping }
      }));
    }
  });
}

你是在 更新状态,而不是 “patch 控件”。这种区别会改变你对系统的思考方式。

检查字段状态

checkoutForm.customer.email().valid();   // true | false
checkoutForm.customer.email().invalid(); // true | false

每个字段直接暴露响应式状态。

结论

Signal Forms 将表单转换为 类型化、响应式的数据模型,您可以像处理其他应用状态一样对其进行验证、计算和副作用跟踪。其结果是 更清晰的思维模型更少的样板代码,以及 更高的架构一致性,遍及整个 Angular 应用。

tForm.customer.email().touched();
checkoutForm.customer.email().errors();

模板示例

@if (checkoutForm.customer.email().invalid() &&
     checkoutForm.customer.email().touched()) {
  @for (error of checkoutForm.customer.email().errors(); track error) {
    
{{ error.message }}

  }
}

不同之处在于,这种状态是 基于信号 的,而非命令式的。

  • 您不需要请求表单重新计算。
  • 您不需要触发变更检测。
  • 它会自行响应。

最大的变化 不是 语法,而是概念层面。

响应式表单引入了并行抽象

  • 控件树
  • 验证系统
  • 状态系统
  • 事件系统

Signal Forms 将所有这些合并为

  • 响应式状态 + 架构验证。

如果您已经在使用:

  • 信号
  • 计算值
  • 副作用
  • 无 Zone 的 Angular

那么 Signal Forms 会显得一致——而这种一致性在大型代码库中尤为重要。

注意: Signal Forms 在 Angular 21 中仍属实验性功能。我不会明天就重写一个大型企业应用,但对于全新 Angular 21 项目,我绝对会从它开始。

何时采用 Signal Forms

  • 您已经 以信号为先
  • 您想避免繁重的 RxJS 表单逻辑
  • 您希望 更简洁的思维模型
  • 您关注 细粒度的响应性

API 小巧,概念一致,类型强大,思维负担更低。

多年来,Angular 表单虽强大却沉重。
Signal Forms 更轻盈、更贴合现代 Angular、更明确且不神秘。它们鼓励您正确地建模业务域,而不是仅仅拼接控件树。

如果您曾经需要调试一个带有动态验证器的深层嵌套响应式表单,您会立刻体会到两者的差异。

这不仅仅是一个新的表单 API。它标志着 Angular 在引入信号后完成了最初的转变。表单不再是一个特殊子系统——它们只是响应式状态。我真诚地认为这才是正确的方向。

0 浏览
Back to Blog

相关文章

阅读更多 »