Angular 的响应式编程并不是从 Signals 才开始
为什么这种说法在历史上并不准确——以及它为什么会在真实项目中产生危险。
一个很常见的说法是:
“Angular 的响应式编程,不就是从 Signals 才开始的吗?”
乍一听,这个说法似乎很合理。
从 Angular 16 开始,signal()、computed() 和 effect() 这些 API 直接进入了 Angular Core。响应式这个概念因此在语言和框架设计上都变得更加显眼。如果一个人直到这代 Angular 才真正深入使用框架,很容易形成这样的印象:Signals 之前的 Angular 主要是组件式、面向 Class 的;Signals 出现之后,Angular 才变得响应式。
问题在于,这种解释把“更明显”误当成了“从这里开始”。
Signals 并没有让 Angular 才变得响应式。
Signals 给 Angular 增加了一种新的、Framework-native 的响应式 Primitive。
这是一个非常重要的区别。

为什么这种说法有危险
Section titled “为什么这种说法有危险”问题不只是历史描述不准确。
真正危险的是,它很容易在项目里变成一个方便的借口。
如果响应式编程真的是从 Angular 16 才出现的,那在 Angular 2、4、8、12 或 15 的年代,团队当然“没必要”去学它。于是,命令式组件、手工 Subscription、本地 Mutation 和难以理解的数据流,就不再是架构选择的结果,而会被解释成“当年的 Framework 就是这样”。
这很方便。
但并不正确。
类似的说法,常常会变成一面挡住不舒服问题的盾牌:
- 为什么项目从来没有真正用好
ObservableStream? - 为什么没有清晰的 ViewModel?
- 为什么业务逻辑在 Component 里?
- 为什么 State 要靠手工同步?
- 为什么代码里到处都是
subscribe()? - 为什么 Read State 和 Command 没有明确分离?
- 为什么 Template 里充满逻辑?
- 为什么几年以后 Refactoring 几乎做不动?
于是,答案不再是:
“我们没有真正理解、学习或者重视这个 Paradigm。”
而变成:
“那个时候根本还没有这些东西。”
危险就在这里。
因为诊断一旦错误,后面的决策也会跟着错误。

真正的损害不在一句话,而在代码库
Section titled “真正的损害不在一句话,而在代码库”一句错误的话,当然不会直接造出糟糕架构。
但如果类似的说法连续多年被用来合理化技术决策,最后就会形成越来越难以移动的代码库。
一个维护了七年的 Angular 项目,可能会变成这样:
- 几千行的 Component
- 到处都是命令式 Loading State 和 Error State
- 嵌套 Subscription
- 字段之间靠手工同步
- Service 变成全局数据仓库
- Template 里有函数调用和隐藏逻辑
- 没有清晰的业务边界
- 没有一致的 State Model
- Presentation Logic 几乎无法测试
- 每次修改成本都很高
- 团队害怕 Framework Upgrade
- 依赖很多从未被有意识选择过的旧 Pattern
然后就会出现一个很苦涩的矛盾:
团队被这个 Framework 牢牢绑定,却从来没有真正使用好它最核心的一部分理念。
这才是真正的 Framework Lock-in。
不是因为 Angular 天生把人锁住。
而是因为代码库长期逆着 Framework 的 Paradigm 生长,以至于任何现代化都变得昂贵。
更准确的事实
Section titled “更准确的事实”历史上更准确的说法是:
Angular 并不是从 Signals 才开始响应式。
从 Angular 2 起,它就已经明显受到 Observable 和 RxJS 的影响。
从 Angular 16 开始,Signals 又为这个模型增加了一种 Framework-native、细粒度的响应式 Primitive。
这才是核心事实。
它很重要,因为它重新把责任放回正确的位置。
Angular 2 到 15 并不是“响应式之前的 Angular”。
它们是 Observable-based Angular。
如果一个团队在那个时期把 Angular 完全写成命令式,并不能简单说那只是“当年的 Angular Style”。这意味着 Framework 的重要概念被忽略,或者只被很表面地使用。
这不是道德指责。
但它是一项重要的技术诊断。
真正的历史节点:Angular 2 已经是响应式转折
Section titled “真正的历史节点:Angular 2 已经是响应式转折”要理解这个误区,首先必须把 Angular 和 AngularJS 分开。
AngularJS 很大程度上由 $q、Promise、$http、$scope、$watch 和 Digest Cycle 所塑造。AngularJS 文档把 $q 描述为一个与 Promises/A+ 兼容的 Promise 与 Deferred Objects 实现。
那是 AngularJS 的世界:异步流程经常通过 Promise 建模,再由 Digest Cycle 反映到 UI。
Angular 2 在这里并不是一次小升级。
Angular 2 是一次断裂。
早期 Angular 2 文档已经明确说明,HTTP Client 的 http.get() 返回的不是 Promise,而是来自 RxJS 的 Observable<Response>。文档紧接着介绍 RxJS,并指出 Observable 会在 Angular 应用中被广泛使用。
来源:Angular v2 Archive: HTTP Client / RxJS library
Angular 2 的 Glossary 也直接把 Observable 放在 Angular 本身的语境里,并把它和 Angular 的 Event System、HTTP Client 等概念联系起来。
来源:Angular v2 Archive: Glossary / Observable
Template Binding 同样已经具备响应式使用方式。旧版 Angular 2 的 AsyncPipe 文档说明,AsyncPipe 可以接收 Promise 或 Observable,会自动 Subscribe,并把 Emitted Value 交给 Template。
来源:Angular v2 Archive: Pipes / AsyncPipe
这才是关键的历史节点:
Angular 并不是从 Signals 才开始响应式。
Angular 2 本身就已经是从 AngularJS 的 Promise 世界,转向 Observable / RxJS 架构的一次明显变化。
Signals 是很久以后才出现的。
它们没有把“响应式”这个思想第一次带进 Angular,而是补充了另一种响应式模型:更贴近 Framework、更同步、更细粒度,也更适合本地 State 和 Derived State。

所以,正确的时间线并不是:
AngularJS:命令式
Angular 2 到 15:不响应式
Angular 16:终于响应式
而更接近:
AngularJS:主要受 Promise、
$q和 Digest Cycle 影响
Angular 2:以 Observable / RxJS 为重要基础的重新设计
Angular 16:Signals 作为额外的 Framework-native 响应式 Primitive
这样一来,这个 Myth 本身就站不住脚。
因为说:
“Angular 的响应式编程从 Signals 才开始。”
实际上跳过了 Angular 2 的一个核心架构选择。
Angular 2 并不是“顺便允许”Observable 存在。HTTP Client、文档、Event Model 和 Template Binding 都明确暴露了这种设计。
这不是边缘细节。
而是相对 AngularJS 的一次 Paradigm Shift。
| 阶段 | 主要模型 | 含义 |
|---|---|---|
| AngularJS | $q、Promise、$http、$scope、$watch、Digest Cycle | 异步流程经常通过 Promise 建模,再由 Digest Cycle 接入 UI。 |
| Angular 2 | RxJS、Observable、Http、Event System、AsyncPipe | Angular 明显转向以 Observable / RxJS 为核心组成部分的架构。 |
| Angular 16 | Signals、computed、effect、RxJS Interop | Angular 增加 Framework-native、细粒度的响应式 Primitive。 |
这个时间线很重要。
Signals 不是 AngularJS 的直接反面。
Angular 2 已经是对 AngularJS 的重新设计。
Signals 是 Angular 后续演进中的下一步。
今天的 Angular 依然大量使用 Observable
Section titled “今天的 Angular 依然大量使用 Observable”Observable Model 并不是某个已经被 Signals 淘汰的历史偶然。
Angular 文档至今仍把 Observable 描述为 Angular 处理典型异步操作的重要接口:HTTP 使用 Observable 表达 Request 和 Response,Router 和 Forms 也通过 Observable 响应 User Input Event。
来源:Angular Docs: Observables in Angular
当前 HttpClient 文档同样明确说明,请求方法返回 RxJS Observable。
来源:Angular Docs: Making HTTP requests
HttpClient 的 API 文档也列出了 Observable<T>、Observable<HttpEvent<T>>、Observable<HttpResponse<T>> 等返回类型。
因此,RxJS 并不只是过去留下来的 Legacy。
它今天依然是 Angular 日常开发的一部分。
Signals 真正带来了什么
Section titled “Signals 真正带来了什么”这并不意味着 Signals 不重要。
Angular 16 引入了 Signals:新的 Signals Library 被放进 @angular/core,同时还通过 @angular/core/rxjs-interop 提供了和 RxJS 之间的 Interop。
来源:Angular Blog: Angular v16 is here
当前 Signals 文档把 Signal 描述为一种可以细粒度追踪 State 在哪里、以什么方式被使用的系统,从而帮助 Angular 优化 Rendering Update。
典型的 Signal API 是:
const count = signal(0);
const doubled = computed(() => count() * 2);
effect(() => { console.log('Count changed:', count());});这和 RxJS Stream 是另一种形式的响应式。
Signals 特别适合同步、本地和可派生的 State:
- UI State
- Component 内部状态
- Derived Value
- 细粒度更新
- 简单的值依赖关系
- 某些 State Relationship 更直接的可读性
RxJS 则特别擅长异步数据流:
- HTTP
- Router State
- WebSocket
- Event
- Form
- Debouncing
- Cancellation
- 多个数据源组合
- 复杂的时间关系
两者都是响应式。
只是解决的问题不同。

Angular 自己也没有把 RxJS 和 Signals 当成二选一
Section titled “Angular 自己也没有把 RxJS 和 Signals 当成二选一”Angular 提供了正式的 Interop Layer,用来连接 RxJS 和 Signals。
当前 @angular/core/rxjs-interop 文档介绍了 toSignal():它可以创建一个 Signal,并持续跟踪某个 Observable 的值。文档还把这种行为和 async pipe 做了比较,只是 toSignal() 更灵活,也可以在 Template 之外使用。
来源:Angular Docs: RxJS interop with Angular signals
这同样直接反驳了那个 Myth。
如果 Signals 真的是 Angular 第一次拥有的“真正响应式”,Angular 就没有必要专门提供 Observable 与 Signal 之间的官方桥梁。
这座桥存在,恰恰是因为 Angular 在历史上一直大量使用 Observable,而 Signals 并不是简单把它替换掉,而是补充了新的模型。
为什么这个误区会出现
Section titled “为什么这个误区会出现”这个误区通常出现于一种情况:很多人一直在“使用 Angular”,但从来没有真正系统地理解它背后的响应式 Paradigm。
Angular 完全可以被写成下面这样:
this.http.get<User[]>('/api/users').subscribe((users) => { this.users = users;});它能运行。
但这本身还不是响应式架构模型。
很多时候,它只是“开头有一个 Observable 的命令式代码”。

响应式 Angular 不只是问:
我怎么拿到这个值?
它还会问:
- State 从哪里来?
- 它如何随时间变化?
- 哪些是 Derived State?
- 什么是 Command?
- 什么是 Read Model?
- 哪些内容应该进入 ViewModel?
- Template 最后应该只消费什么?
- 哪些依赖关系应该被显式建模?
- Presentation Logic 到哪里结束?
- 业务逻辑从哪里开始?
这些问题在 Signals 出现以前,RxJS 就已经可以支持。
Observable、map、switchMap、combineLatest、shareReplay、AsyncPipe、Facade、Store 和 ViewModel,并不是 Angular 16 才突然变得可能。
只是很多团队从来没有真正持续地利用这些能力。
错误叙事会保护糟糕决策
Section titled “错误叙事会保护糟糕决策”技术上的 Half-truth 很少只是知识漏洞。
在项目里,它们经常会变成保护罩。
保护旧决策不被重新审视。 保护已经长成某种样子的代码库,不必面对难受的问题。 保护“前端只是 UI Layer”之类的角色理解。 保护团队不必真正追踪文档、Paradigm 和 Framework 的演进。
问题不在于某个人不知道某件事。
问题从“不知道”变成“架构理由”的时候才真正开始。
“那个时候做不到。”
这是一个很强的说法。
如果它是错的,就会遮住真正原因。
也许当时其实可以。 也许文档已经写得很清楚。 也许甚至已经是推荐方式。 只是团队没有学习、没有理解,或者没有把它放在优先级上。
这很不舒服。
但在诚实的技术分析里,这个区别非常重要。
最后受影响的是产品
Section titled “最后受影响的是产品”糟糕架构不会只留在代码里。
它会影响产品。
如果一个 Angular Frontend 多年来一直以命令式、缺少结构、逆着响应式数据流的方式发展,那么每次业务变化都会越来越昂贵:
- 新 Feature 需要更久
- Bug 更难复现
- 副作用越来越难追踪
- Refactoring 风险越来越高
- 测试停留在表面
- UI State 越来越不一致
- Release 越来越保守
- 技术债开始决定产品规划
最终,不再是产品决定应该做什么。
而是代码库决定什么还做得起。
到了这里,架构就不再只是开发团队内部的话题。
它已经变成产品问题。
RxJS 对 Signals,是错误的争论方式
Section titled “RxJS 对 Signals,是错误的争论方式”真正有意思的问题不是:
RxJS 还是 Signals?
更好的问题是:
哪一种响应式模型适合哪一种问题?
一个由搜索词触发、需要取消旧 Request 的 HTTP Flow,通常非常适合 RxJS。
一个本地 UI State、Derived Value 或简单的 Component State Relationship,通常非常适合 Signal。
一个 Store 可能同时接触这两种世界。
Facade 可以消费 Observable,再暴露 Signal。
Signal 可以由 Observable 创建。
Observable 也可以由 Signal 创建。
Angular 自己提供官方 Interop。这已经足以说明,Framework 并没有把 RxJS 和 Signals 当成二选一。
真正的架构问题
Section titled “真正的架构问题”好的 Angular 架构,并不会因为代码里到处写了 signal() 就自动出现。
它来自清晰的职责。
比具体 API 更重要的是这些问题:
- 业务逻辑是否和 Template 分离?
- 是否有清晰的 ViewModel?
- Command 和 Read State 是否分开?
- 数据流是否可追踪?
- 副作用是否受控?
- Derived State 是真的派生出来的,还是靠手工同步?
- Component 是消费者,还是垃圾场?
- Service 有清晰边界,还是只是 Manager Class?
- 代码是否可测试?
- 架构是否容易理解?
具体 State 最后通过 RxJS、Signals、NgRx Signal Store,还是它们的组合来实现,是一个实现层问题。
背后的 Paradigm 比 API 更重要。

不要说:
“Angular 的响应式编程是从 Signals 才开始的。”
更准确的说法是:
“从 Angular 2 起,Angular 就已经明显受到 Observable 和 RxJS 影响;从 Angular 16 开始,Signals 又为这个模型增加了一种 Framework-native、细粒度的响应式 Primitive。”
这在历史上更准确。
它既承认 Signals 的价值,也不会把之前那些年从技术历史里抹掉。
更重要的是,它不会用一段错误的 Framework 历史,替过去的架构选择做事后合理化。
与其说:
“当年的 Angular 本来就不响应式。”
不如问:
“那个时候我们已经拥有哪些响应式能力?为什么没有使用它们?”
这个问题更让人不舒服。
但也更有价值。
因为它不会把团队带向借口,而是带向学习:
- 我们忽略了哪些 Paradigm?
- 哪些 Pattern 用错了?
- 哪些决定当时是有意识的?
- 哪些只是习惯?
- 代码库哪些地方现在需要先稳定下来?
- 今天我们需要补哪些概念,才能重新获得可变更性?
这时,架构工作才真正开始。
不是为了追责。
而是为了得到更准确的诊断。
“Angular 的响应式编程从 Signals 才开始”这个说法,在历史和技术上都不准确。
从 Angular 2 起,Angular 就已经明显采用 Observable 和 RxJS。官方 Angular 2 文档在 HTTP Client、Event System 和 AsyncPipe 等位置都明确介绍了 Observable。今天的 Angular 文档也仍然让 HttpClient 返回 Observable。Angular 16 引入的,是新的 Framework-native 响应式 Primitive——Signals,同时还提供正式的 RxJS Interop。
因此,Signals 不是 Angular 响应式编程的出生证明。
它是一个新章节。
一个重要的章节。 一个很好的章节。 但不是第一章。
也正因为如此,这个 Myth 才危险。
它把“没有学会”解释成 Framework 的限制。 把架构上的错失解释成历史必然。 也让旧 Angular 代码库逃过了一个本来应该被认真问的问题:
哪些事情当时真的被 Angular 限制了——哪些只是我们从来没有真正学会?
这个问题很不舒服。
但如果不问,人们被困住的就不只是 Framework。
还会被困在自己的过去里。
- AngularJS Docs: $q
- Angular v2 Archive: HTTP Client / RxJS library
- Angular v2 Archive: Glossary / Observable
- Angular v2 Archive: Pipes / AsyncPipe
- Angular Docs: Observables in Angular
- Angular Docs: Making HTTP requests
- Angular API: HttpClient
- Angular Blog: Angular v16 is here
- Angular Docs: Signals
- Angular Docs: RxJS interop with Angular signals
资料核查于 2026 年 6 月 10 日。