操作员手册:从 v17.3 到 v21 的 Angular Signals 导航
Source: Dev.to
请提供您希望翻译的正文内容,我将按照要求将其译成简体中文并保留原有的格式、Markdown 语法以及技术术语。谢谢!
Version 1.0
| Section | Title |
|---|---|
| 1.1 | 基于 Signal 的组件:完整工具箱 |
| 1.2 | 使用 output() 和 RxJS 互操作 |
| 2.1 | 从预览到正式:升级到稳定版 |
| 2.2 | Signal 驱动的服务模式 |
| 2.3 | 实现无 Zone 的未来 |
| 3.1 | 引入 Signal 表单:范式转变 |
| 3.2 | 高级操作:异步数据与关联状态 |
| 4.1 | Signal‑基代码的核心原则 |
| 4.2 | 升级你的系统:迁移路径 |
| 4.3 | 迁移清单:一步步指南 |
| 4.4 | 采用 signal 组件 – 用直接的 signal 调用替代 async pipe |
| 4.5 | 将服务从 BehaviorSubject 重构为 signal |
| Conclusion | 结论:Angular 响应式的新纪元 |
| References | — |
Introduction
欢迎,操作员。您已经获得了一台经历了深刻变革的机器的钥匙。最初在 Angular 中作为全新响应式原语出现的技术,已演变为一个完整集成的系统,重新定义了我们构建、管理和思考应用状态的方式。本手册将指导您掌握 Angular Signals 的演进——从 v17.3 中的基础 API 到 v21 的实验前沿。准备好重新校准您对响应式的理解吧。
1️⃣ Angular 17.3 – 第一个完整去装饰器的发布
Angular 17.3 提供了创建完全无装饰器、基于信号的组件的最后一块拼图。
- 在 17.3 之前,Angular 已经提供了基于信号的 inputs(
input())和 queries(viewChild())替代方案。 - 组件 outputs 仍然依赖传统的
@Output()装饰器和EventEmitter。
1.1 output() – 新的原语
Angular 17.3 引入了 output() 函数,使开发者能够编写所有 面向公共 API——inputs、outputs 和 queries——都以函数形式定义的组件。这创造了一致的、无装饰器的体验,并在概念上与其他基于信号的 API 对齐。
// Before: Angular ();
onClick() {
this.clicked.emit();
}
// After: Angular 17.3+
import { Component, output } from '@angular/core';
@Component({ /* … */ })
export class SignalButtonComponent {
clicked = output();
onClick() {
this.clicked.emit();
}
}
output() 提供了一种轻量级、独立于 RxJS 的事件发射方式。虽然 EventEmitter 继承自 RxJS 的 Subject,但 output() 返回的新的 OutputEmitterRef 是一个更简洁、专用的原语。
2️⃣ RxJS Interop
Angular 团队提供了一个桥接,帮助在庞大的基于 RxJS 的代码生态中使用 @angular/core/rxjs-interop 包,该包现在包括:
| 实用工具 | 目的 |
|---|---|
outputFromObservable() | 将 RxJS 流转换为组件输出 |
outputToObservable() | 将组件输出转换回 RxJS Observable |
这些实用工具实现了 平滑、增量的迁移,无需对现有的响应式逻辑进行全面重写。
3️⃣ Angular 18 – 20 – 加固信号与新架构模式
Angular 18 到 20 期间的重点是 稳定信号 API 并建立新的架构模式。
- 稳定的原语:
input()、model()(双向绑定)以及基于信号的查询(viewChild()、contentChildren())从开发者预览升级为稳定版。 - v20 里程碑:所有基础响应式原语均被视为稳定,为企业长期采用信号提供了信心。
3.1 Signal‑In‑A‑Service 模式
不再在服务中使用 BehaviorSubject,开发者现在采用 signal‑in‑a‑service 方法:
import { Injectable, signal, computed } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class ItemService {
// 私有、可写的 signal
private _items = signal([]);
// 公有、只读的 signal
readonly items = this._items.asReadonly();
// 用于派生状态的计算 signal
readonly total = computed(() => this._items().length);
// 公有方法用于变更状态
addItem(item: string) {
this._items.update(items => [...items, item]);
}
}
好处
- 干净的 单向数据流 —— 服务是唯一的真相来源。
- 消费者在 不变更 底层数据的情况下响应状态变化。
- 信号的同步特性提升 可测试性,并消除与 RxJS subjects 与订阅相关的样板代码。
3.2 无 Zone 的未来
传统 Angular 依赖 Zone.js 在任何异步操作后触发变更检测。虽然便利,但可能效率不高。
- 信号提供细粒度的依赖图 —— Angular 能精确知道哪些组件/计算值依赖于被修改的信号,只更新这些部分。
- 无 Zone 模式 在 Angular v20.2 中成为稳定版,并在 Angular v21 中设为 默认。
优势
- 更小的 bundle 大小。
- 更佳的运行时性能。
- 更可预测的应用行为。
4️⃣ Angular 21 – 下一代进化
在核心原语稳定的情况下,Angular 21 探索了一个 基于信号的丰富生态系统,旨在解决复杂的 Web 开发挑战(例如,信号表单、异步数据处理、关联状态)。这些工具将信号范式扩展到超越简单状态管理,使开发者能够使用统一的响应式模型构建 高性能、易维护的应用程序。
5️⃣ 迁移检查清单 – 步骤指南
| ✅ Step | Action |
|---|---|
| 1 | 在所有组件中将 @Input() 替换为 input()。 |
| 2 | 将 @Output() 替换为 output()。 |
| 3 | 将基于 EventEmitter 的输出转换为 output(),并在需要 RxJS 互操作时使用 outputFromObservable() / outputToObservable()。 |
| 4 | 重构服务:将 BehaviorSubject/ReplaySubject 替换为 私有可写信号 (signal()) 并公开 只读/计算信号。 |
| 5 | 更新模板:将 async 管道替换为直接信号调用 ({{ mySignal() }})。 |
| 6 | 启用 无区模式 (enableProdMode(); setZoneEnabled(false);) 并测试变更检测行为。 |
| 7 | 运行 Angular 迁移脚本 (ng update @angular/core@21)。 |
| 8 | 验证单元和集成测试;调整所有依赖 RxJS 订阅副作用的测试。 |
| 9 | 审查性能指标;确保包体积缩小和持续交付周期改进。 |
| 10 | 为团队记录新模式并更新入职材料。 |
结论
Signals 已经将 Angular 从一个 zone‑centric、RxJS‑heavy 框架 转变为一个 fine‑grained、declarative 且 performant 平台。通过拥抱基于信号的 API,采用 signal‑in‑a‑service 模式,并启用 zoneless mode,您可以为您的应用程序在下一代 Web 开发中奠定基础。
References
- Angular 官方文档 – Signals(v17‑v21)
- Angular 路线图 – 响应式 & 无 Zone 更新
@angular/core/rxjs-interopAPI 文档- 示例仓库: https://github.com/leolanese/Angular-Zoneless-Signals
Source: …
Angular 中的 Signals – 未来一瞥
实验性的 signals 提供了一个预览,展示了一个将响应式无缝集成到框架每个部分的未来。
新的 Signal Forms API(Angular 21)
可通过
@angular/forms/signals实验性使用。
Signal Forms 颠覆了传统思路:不再先构建与数据匹配的表单结构,而是从数据模型出发,让表单以响应式方式自行衍生。
模型优先(Model‑First)方式
// 将数据模型定义为 signal
user = signal({ firstName: '', email: '' });
// 根据模型创建表单
profileForm = form(this.user, (path) => [
// …验证规则
]);
声明式验证
验证规则在表单的 schema 中以函数形式定义:
- 内置验证器(
required、minLength、email) - 条件验证(
when属性) - 跨字段验证
这取代了传统表单中添加/移除验证器的命令式逻辑。
简化的模板绑定
旧的 formControlName 被全新的 [field] 指令 取代,在模板与表单字段 signal 之间建立直接、类型安全的链接。
内置提交处理
submit() 管理提交过程,自动处理加载状态(submitting())和服务器端错误,并可直接映射回表单字段。
结果: 大幅减少样板代码,提升类型安全性,验证逻辑更直观、易于维护。
其他实验性基于 Signal 的工具
| 工具 | 描述 |
|---|---|
Resource API (resource, rxResource) | 声明式异步数据获取。当依赖的 signals(例如 params)变化时,资源会重新获取,并提供用于加载、错误和数值状态的内置 signals。 |
linkedSignal() | 一种高级原语,可写(不同于只读的 computed())。它从源 signal 派生初始值,但也可以手动更新——适用于例如下拉列表在选项变化时重置,但仍允许手动选择的场景。 |
Source: …
第 4 章 – 操作员实用指南:最佳实践与迁移
掌握新系统需要了解其交互规则。本章提供关键原则和升级现有机器的战略计划。遵循这些最佳实践可让基于 signal 的应用保持 高性能、可预测、易维护。
核心规则
- 使用
computed()处理派生状态 – 最重要的规则。如果一个值是由一个或多个 signal 计算得出的,始终使用computed()。避免使用effect()去设置另一个 signal(这是一种会导致性能问题和意外行为的反模式)。 - 仅在副作用时使用
effect()– 用于桥接非 signal 世界(例如日志记录、同步到localStorage、自定义 DOM 操作)。请记住,effect 是异步执行的。 - 保持状态扁平化 – 避免在 signal 中嵌套其他 signal 或深层对象。扁平化的状态结构更易追踪,也能实现更高效的变更检测。
4.2 升级机器与迁移路径
生成迁移示意图
# 将 @Input() 迁移为 signal 输入
ng generate @angular/core:signal-input-migration
# 将 @Output() 迁移为 signal 输出
ng generate @angular/core:signal-output-migration
# 将 @ViewChild/@ContentChild 查询迁移
ng generate @angular/core:signal-queries-migration
采用实验性的 Signal Forms API
-
确定要迁移的传统响应式表单。
-
为数据模型定义一个 signal:
user = signal({ name: '', email: '' }); -
创建 signal 表单:
profileForm = form(this.user, /* schema */); -
更新模板 – 用
[field]指令替换formControlName。 -
用
form()函数内部的声明式规则取代命令式验证。
Angular 17.3 新特性 – 作者 Gergely Szerovay
- 新的
output()API - RxJS 互操作帮助函数(
outputFromObservable,outputToObservable) HostAttributeToken- 支持 TypeScript 5.4
基于 Signal 的 Angular 架构:构建更智能的购物车
一本实用指南,介绍如何使用 “signal‑in‑a‑service” 模式重构状态繁重的功能,以实现更简洁、更易测试且性能更佳的应用。
基于 Signal 的 Angular 表单:它们将彻底改变你对表单处理的认知
提前了解 Angular 21 中实验性的 Signal Forms API,重点展示其 模型优先设计、声明式验证 和 类型安全的模板绑定。
资源
- Reactive Angular: Loading Data with the Resource API – 介绍实验性的
resource和httpResource原语,用于管理带有内置加载、错误和值状态信号的异步数据流。 - Dependent state with
linkedSignal– Angular Official Guide – 关于linkedSignal()的文档,它是可写的类似计算的信号,在其源变化时会重置——非常适合级联下拉框等动态 UI 控件。 - Angular Signals
Effect(): Why 90% of Developers Use It Wrong – 阐明了effect()的常见反模式,并强调它应仅用于副作用(例如日志记录、localStorage、DOM 变更)。 - Angular Roadmap – Official angular.dev – Angular 战略优先级的权威来源,包含关于 Signals、无区块(zoneless)Angular、Signal Forms、HMR、测试工具以及未来探索的状态更新。
- Meet Angular’s new
output()API – Angular Blog – Angular 17.3 中output()函数的官方公告,解释了其类型安全、与信号输入的对齐,以及通过outputFromObservable()和outputToObservable()与 RxJS 的互操作。 - Angular v21 Goes Zoneless by Default: What Changes, Why It’s Faster, and How To – 对 Angular v21 默认采用无区块(zoneless)变更检测的深入分析——涵盖性能提升、包体积缩小、迁移步骤以及注意事项。
让我们保持联系!
- LinkedIn: LeoLanese
- Twitter: @LeoLanese
- Blog: Dev.to
- Contact: developer@leolanese.com