傳統 Angular 在監測資料變化時,通常會搭配 zone.js。像是 DOM 事件、Promise、Timer、HTTP 請求這些非同步操作發生時,zone.js 會幫忙攔截。
當這些操作執行完成後,Angular 就會知道:「現在資料有可能發生變化了」,接著啟動一輪 Change Detection,檢查畫面是不是需要更新。
變更偵測開始後,Angular 會重新檢查元件 Template 裡的資料綁定,看看目前的值和上一次是否不同。如果真的有變化,就會更新對應的畫面。
Angular 傳統的變更偵測策略主要可以分成:
Change Detection,只要遍歷到這個元件,就會檢查它的 Template binding,確認資料是否發生變化。Input 發生變化、元件內事件被觸發、透過 markForCheck() 標記,或 AsyncPipe 收到新的值。
Signal 是 Angular 在響應式機制上的一個很重要的轉折點。對開發者來說,當我們在 Template 中使用一個響應式變數時,通常會很直覺地認為,只要這個值發生改變,畫面就會自動更新。
但實際上,不同框架背後採用的更新策略並不相同。要理解 Angular Signal,可以先拆成兩件事情來看:
Signal 被誰使用,也就是如何建立與追蹤依賴關係Signal 改變之後,我們怎麼知道哪些 View 可能受到影響這兩件事情其實是分開處理的。Signal 主要負責精確追蹤資料之間的依賴關係。當 Template 在執行過程中讀取某個 Signal 時,Angular 會記錄這個 View 與 Signal 之間的依賴關係。
當 Signal 的值改變時,它不會直接操作 DOM,而是通知依賴這個 Signal 的 Consumer。Angular 接著會標記相關的 View 與祖先路徑,讓後續的 Change Detection 能沿著 View tree 找到這個可能受到影響的 View,再進一步判斷是否需要重新執行對應的 Template binding。
重新檢查時,Angular 會將這一次計算出的值與上一次的 binding value 進行比較,只有當值真的發生變化時,才會進一步更新實際的 DOM。
因此,Signal 解決的核心問題是「誰受到這次資料變更的影響」,而真正「DOM 要不要更新」這件事情,仍然會交由 Angular 的變更偵測機制處理。

前面我們知道,Signal 並不是在資料改變時直接去操作 DOM,而是先找出「有哪些地方依賴這個 Signal」,再交給 Angular 後續的 Change Detection 處理。
如果把整個流程拆開來看,大致可以分成以下幾個步驟:
Step 1:Template 讀取 Signal:建立依賴關係(今天主要說明這個部分)
Step 2:Signal 發生變化:確認是否需要傳遞變更
Step 3:標記受到影響的 View 與祖先路徑
Step 4:Angular 執行 Change Detection
Step 5:比較 binding value 並更新 DOM
Signal:建立依賴關係當 Angular Template 執行時,如果讀取了一個 Signal,Angular 會記錄這個 Signal 與目前 Template View 之間的依賴關係。

在這個響應式系統中,可以先理解兩個重要角色:
Producer:負責提供響應式資料,例如 signal()。Consumer:負責讀取 Producer 的資料。當 Consumer 讀取 Producer 時,Angular 會建立兩者之間的依賴關係。我們先以 AppComponent 為例:
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
template: `
<p>目前數量:{{ count() }}</p>
`,
})
export class AppComponent {
count = signal(0);
}
當 Template 執行到 count() 時,Angular 會知道目前的 Template View 正在讀取 count 這個 Signal,並建立兩者之間的依賴關係。
這個過程可以簡化理解成:
count Signal
Producer
│
│ Template 執行 count()
▼
AppComponent Template View
Consumer
這是 Signal 更新流程的第一步:在讀取 Signal 時建立依賴關係。隨著不同的 Signal 被讀取,Angular 會持續記錄 Producer 與 Consumer 之間的關係,最後形成一張 Reactive Graph,用來描述整個響應式系統中的依賴關係。
PS:實際上 Angular 底層的依賴追蹤機制會更加複雜,不過目前可以先用 Producer、Consumer 與 Template View 之間的關係來理解整體概念。
實際上,Angular 底層還會多一層 Reactive Consumer 的處理。Template View 並不是直接和 Signal 建立依賴,而是透過一個與 LView 關聯的 Consumer 來參與 Reactive Graph。
補充:
LView是 Angular 在執行期間,用來保存 View 狀態與相關執行資料的內部結構。
LView 取得對應的 ReactiveLViewConsumer
export function getOrBorrowReactiveLViewConsumer(
lView: LView,
): ReactiveLViewConsumer {
return (
lView[REACTIVE_TEMPLATE_CONSUMER] ??
borrowReactiveLViewConsumer(lView)
);
}
如果這個 LView 已經有對應的 Consumer,就直接沿用;如果還沒有,Angular 會取得一個可用的 ReactiveLViewConsumer
因此,需要參與 Signal 依賴追蹤的 View,會透過 ReactiveLViewConsumer 加入 Reactive Graph,並由這個 Consumer 記錄目前 View 所依賴的 Signal。
當 count 這類 Signal Producer 發生變化時,Angular 就能沿著依賴關係找到對應的 Consumer,進而得知哪些 View 可能受到這次資料變更影響,而需要重新檢查。
Consumer 本身也會以 ReactiveNode 的形式存在於 Reactive Graph 中
ReactiveNode可以理解成Reactive Graph裡的一個節點,而Producer與Consumer則是這個節點在依賴關係中扮演的角色。有些節點只會是Producer或Consumer,也有些節點會同時具備兩種角色,例如computed()。
export const REACTIVE_LVIEW_CONSUMER_NODE = {
...REACTIVE_NODE,
consumerIsAlwaysLive: true,
kind: 'template',
consumerMarkedDirty: (node: ReactiveLViewConsumer) => {
markAncestorsForTraversal(node.lView!);
},
consumerOnSignalRead(this: ReactiveLViewConsumer): void {
this.lView![REACTIVE_TEMPLATE_CONSUMER] = this;
},
};
這邊有提供兩個 Hook:
Signal 時觸發,將目前的 ReactiveLViewConsumer 記錄到對應的 LView 上,讓 Angular 能在後續流程中取得這個 Consumer,持續參與 Signal 的依賴追蹤。Consumer 因為依賴的 Producer 發生變化而被標記為 dirty 時觸發。這個 Hook 會進一步呼叫 markAncestorsForTraversal() 標記祖先路徑,讓後續的 Change Detection 能沿著 View tree 找到這個可能需要重新檢查的 View。第二個 Hook 特別重要,因為這個可能需要重新檢查的 View,可能位在一條原本會被 Change Detection 略過的分支中,例如祖先元件使用 OnPush,而這一輪本身沒有其他原因需要重新檢查。

透過這個 ReactiveNode,Angular 能在 Reactive Graph 中記錄 Consumer 與 Producer 的依賴關係,並在 Producer 發生變化時,找到對應的 Consumer,進一步銜接後續的 View 檢查流程。
整體關係可以先簡化成:
AppComponent LView
│
▼
ReactiveLViewConsumer
Consumer
│
│ depends on
▼
count Signal
Producer
所以實際參與 Reactive Graph 的,是與這個 View 關聯的 ReactiveLViewConsumer。透過這層關係,Angular 才能在 Signal 發生變化時,找到相關的 Consumer,並進一步判斷哪些 View 可能需要重新檢查。
Signal、Reactive Context 與依賴追蹤的基本概念。ReactiveLViewConsumer、consumerMarkedDirty()、consumerOnSignalRead() 等實作。markAncestorsForTraversal() 與 View traversal 相關邏輯。Producer、Consumer、Reactive Graph、Push/Pull 等底層設計。