深入淺出 Angular Signals:原理、用法與 RxJS 的共存之道

更新 發佈閱讀 12 分鐘

1. Change Detection 的演進:從全域檢查到精準更新

Angular 以及現行的其他前端框架,核心任務之一就是透過 Change Detection(變更檢測) 機制,來決定何時該更新網頁的 DOM Tree。

回顧歷史,早期的 AngularJS 採用的是「髒檢查(Dirty Checking)」機制來比對數值變化。到了 Angular (v2+),則改透過 Zone.js 來攔截非同步事件(如 Click、HTTP Request),在事件結束時觸發檢測。此外,雖然引入了 ChangeDetectionStrategy.OnPush 來縮減檢查範圍並提升速度,但當專案規模擴大時,仍面臨兩大核心挑戰:

  1. 由上而下的檢測成本:不可避免地需要從 Root Node 一路傳遞檢查至底層的 Child Nodes。
  2. 對 Zone.js 的強依賴:應用程式必須綁定 Zone.js 才能運作。

為了徹底解決上述問題,Angular 團隊推出了全新的解決方案——Signals

舊時代的挑戰:Zone.js

Zone.js 就像是一個全域的「警報器」。它不知道「什麼」資料變了,只知道「有事情發生了」(例如:滑鼠點擊、定時器結束、API 回傳)。當警報響起,Angular 就必須檢查整個元件樹(Component Tree),確認是否有變數發生異動。

🏢 想像一棟公寓大樓: 如果其中一位住戶重新粉刷了廚房,檢查員(Angular + Zone.js)因為不知道是誰動了手腳,為了確保資料正確,只好去按「每一間」公寓的門鈴,逐一檢查大家的廚房是否變更了。這顯然非常沒有效率。

新時代的救星:Signals

Signals 在「資料(Data)」與「使用資料的地方(Template)」之間,建立了直接的依賴關係圖(Dependency Graph)

當一個 Signal 的值改變時,Angular 能夠精準鎖定並只更新依賴該 Signal 的 DOM 節點。它會直接跳過其餘無關的元件,不再進行多餘的全域檢查。


2. Signal 基本用法

Signal 本質上是一個數值的包裝器 (Wrapper)。當內部的數值改變時,它會主動通知 Angular 進行更新。

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

// 1. 定義 (單一真值來源 / Source of Truth)
count = signal(0);

// 2. 讀取 (重要:永遠記得使用括號來呼叫!)
console.log(this.count());

如何更新數值 (Updating Values)

Signal 提供了兩種寫入數值的方法,視情境選擇:

set(value):完全取代 (Replacement)

this.count.set(5);
  • 適用情境:當你不在乎舊數值,只想直接覆蓋時(例如:重置表單)

update(fn):基於原值的更新 (Evolution)

// current 代表當前的數值 
this.count.update(current => current + 1);
  • 適用情境:當新數值是透過「舊數值」計算而來時(例如:計數器 +1)。

衍生狀態 (Computed Signals)

根據其他的 Signal 自動推導出「新的資料」。它具備 Memoization (智慧快取) 特性:只有當依賴的來源 Signal 改變時,才會重新運算;否則會直接回傳快取值。 (注意:Computed Signal 是唯讀的)

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

price = signal(100);
tax = signal(0.2);

// 當 price 或 tax 任一改變,total 就會自動更新
total = computed(() =>
this.price() * (1 + this.tax())
);

副作用 (Effect)

當 Signal 改變時,自動觸發「與外部世界互動」的操作。它不負責產出資料,而是負責執行行為(例如:寫入 LocalStorage、DOM 操作、記錄 Log)。

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

count = signal(0);
name = signal('Gemini');

constructor() {
// 註冊 effect (必須在 Injection Context 中,如 constructor)
effect(() => {
// Angular 會自動追蹤依賴:偵測到使用了 count() 和 name()
// 只要其中一個改變,這個 block 就會重新執行
console.log(`目前的計數是: ${this.count()}, 用戶是: ${this.name()}`);
});
}

3.元件溝通的新標準 (Signal Inputs & Model)

除了內部狀態,Angular 也將 Signal 的概念延伸到了元件之間的溝通(Inputs/Outputs),讓資料流更加一致。

Signal Inputs (input)

新的 input() 函式取代了傳統的 @Input() 裝飾器。它回傳的是一個唯讀的 Signal,這意味著我們可以直接對 Input 使用 computed,而不再需要 ngOnChanges

import { input, computed } from '@angular/core';

// 舊寫法: @Input() userId: string;
// 新寫法: 自動成為唯讀 Signal
userId = input.required<string>();

// 直接衍生新狀態,當 Input 改變時自動更新
welcomeMessage = computed(() => `Hello, User ${this.userId()}`);

雙向綁定 (model)

以前要實現雙向綁定需要 @Input 加上 @Output。現在只需要一個 model(),它是一個可讀也可寫的 Signal。

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

// 定義一個可讀可寫的 Signal Input
checked = model(false);

toggle() {
// 修改它會自動通知父元件
this.checked.update(v => !v);
}

4. Signals vs RxJS:誰該負責什麼?

很多人會問:「有了 Signals,我還需要 RxJS 嗎?」 其實,它們就像是工具箱裡的「螺絲起子」與「扳手」,各有各的用途。

黃金法則:State vs Events

  1. Signals 是用來「保存數值 (State)」的 它就像一個水桶,隨時去撈都有水。只要你需要顯示在畫面上,或者是一個會變動的變數,選 Signals 就對了。它讓 Template 的寫法變得非常乾淨,不再需要 async pipe。
  2. RxJS 是用來「處理過程 (Events)」的 它就像一條河流 (Stream),強項在於控制水流。當你需要處理「時間」或「事件」時,RxJS 是王者。
  3. 使用情境:
  • 想等使用者停止打字 300ms 後再搜尋?(Debounce)
  • 想取消上一次還沒跑完的 API 請求?(SwitchMap)
  • 這些都是 Signals 做不到,但 RxJS 能輕鬆解決的。

Angular 的新常態:Reactively Unified

未來的 Angular 開發模式將會是「雙劍合璧」:

  1. 用 RxJS 處理邏輯:在 Service 層處理複雜的 API 呼叫與資料流。
  2. 用 Signal 呈現結果:透過 toSignal 把 Observable 轉成 Signal,讓 Component 簡單地「拿資料」。

範例:從 RxJS 到 Signal (toSignal)

import { toSignal } from '@angular/core/rxjs-interop';

// 1. 使用 RxJS 處理搜尋邏輯 (Debounce, SwitchMap)
const searchResults$ = this.searchTerm$.pipe(
debounceTime(300),
switchMap(term => this.apiService.search(term))
);

// 2. 轉成 Signal 給 Template 使用,初始值為空陣列
// Template 中直接使用 {{ results() }} 即可
results = toSignal(searchResults$, { initialValue: [] });

範例:從 Signal 到 RxJS (toObservable)

有時候我們需要監聽 Signal 的變化來觸發 API 請求,這時就需要反向轉換。

import { toObservable } from '@angular/core/rxjs-interop';

// 當 query 這個 signal 改變時,轉成 Stream 進行後續處理
query$ = toObservable(this.query);

this.query$.pipe(
debounceTime(300),
switchMap(q => this.api.search(q))
).subscribe();

5.總結

Signals 的引入,標誌著 Angular 進入了一個全新的「反應式文藝復興」時代。

它不僅解決了 Zone.js 長期以來的效能瓶頸,更透過「細粒度(Fine-grained)」的更新機制,為應用程式帶來了預設的高效能。同時,Signals 與 RxJS 的互補關係,讓我們在處理同步狀態與非同步事件時,擁有更清晰的邊界與工具選擇。

雖然學習新語法需要時間,但這項投資絕對是值得的。隨著 Angular 生態系逐漸向 Signal-based components 轉型,掌握 Signals 將不再只是選項,而是現代 Angular 開發者的必備技能。

留言
avatar-img
留言分享你的想法!
avatar-img
Root Design Lab
26會員
33內容數
「Root Design Lab」結合了「Root」(根源、基礎)、「Design」(設計)與「Lab」(實驗室),象徵這是一個充滿探索精神的創新平台! 我們是UXD(User Experience Design,使用者體驗設計) ,專注於設計與前端技術的發展與實踐,透過實驗與不斷精進,推動創意與技術的無限可能!
Root Design Lab的其他內容
2025/11/22
本文以企業級銀行系統為背景,介紹設計系統從初代到下一代的演進脈絡,並說明為何需以多維策略整合商業、技術與使用者需求。透過重新定義策略、工具布局與技術架構,設計系統將從 UI 工具升級為支撐企業長期數位化與規模化創新的核心基礎建設。
Thumbnail
2025/11/22
本文以企業級銀行系統為背景,介紹設計系統從初代到下一代的演進脈絡,並說明為何需以多維策略整合商業、技術與使用者需求。透過重新定義策略、工具布局與技術架構,設計系統將從 UI 工具升級為支撐企業長期數位化與規模化創新的核心基礎建設。
Thumbnail
2025/11/12
本文介紹 GitHub Copilot 如何透過即時程式碼建議、對話式除錯、程式碼優化等功能,提升開發效率。文章涵蓋 Copilot Chat 的使用方式、模型選擇、檔案選擇策略,以及進階技巧與開發心得,並提醒使用上的注意事項與限制,幫助開發者善用這款工具。
Thumbnail
2025/11/12
本文介紹 GitHub Copilot 如何透過即時程式碼建議、對話式除錯、程式碼優化等功能,提升開發效率。文章涵蓋 Copilot Chat 的使用方式、模型選擇、檔案選擇策略,以及進階技巧與開發心得,並提醒使用上的注意事項與限制,幫助開發者善用這款工具。
Thumbnail
2025/10/27
近年來各類系統紛紛推出資料視覺化功能,儀表板需求也日益增加。在開發中需要快速產出標準圖表?Chart.js 會是你的好夥伴。想要打造高度客製化的視覺效果?D3.js 能讓你實現任何可能。但兩者之間的選擇與權衡往往超乎想像,如何在便利性與靈活性之間找到平衡點?
Thumbnail
2025/10/27
近年來各類系統紛紛推出資料視覺化功能,儀表板需求也日益增加。在開發中需要快速產出標準圖表?Chart.js 會是你的好夥伴。想要打造高度客製化的視覺效果?D3.js 能讓你實現任何可能。但兩者之間的選擇與權衡往往超乎想像,如何在便利性與靈活性之間找到平衡點?
Thumbnail
看更多
你可能也想看
Thumbnail
這篇文章深入淺出地介紹了網路架構中的路由器、NAT 和 Port 的概念,以比喻和生活化的例子說明靜態路由、動態路由、一對一 NAT 和 NAPT 的差異與應用場景,並解釋 Port 的功能和用途。
Thumbnail
這篇文章深入淺出地介紹了網路架構中的路由器、NAT 和 Port 的概念,以比喻和生活化的例子說明靜態路由、動態路由、一對一 NAT 和 NAPT 的差異與應用場景,並解釋 Port 的功能和用途。
Thumbnail
《法華經‧藥王菩薩本事品》重點整理,包含主題意涵、主要內容概述及象徵意義與修行指引,闡述藥王菩薩過去生中精進修行、捨身求法的精神,並強調供養《法華經》的無上功德,鼓勵信眾效法菩薩大悲利他精神。
Thumbnail
《法華經‧藥王菩薩本事品》重點整理,包含主題意涵、主要內容概述及象徵意義與修行指引,闡述藥王菩薩過去生中精進修行、捨身求法的精神,並強調供養《法華經》的無上功德,鼓勵信眾效法菩薩大悲利他精神。
Thumbnail
這篇文章探討非同步編程的優缺點,並提供在設計非同步系統時需要注意的事項。非同步編程允許程式在等待 I/O 操作完成的同時,繼續執行其他工作,提高響應速度和資源利用率。然而,非同步程式設計也增加了系統複雜性,需要謹慎處理錯誤和確保代碼可讀性。
Thumbnail
這篇文章探討非同步編程的優缺點,並提供在設計非同步系統時需要注意的事項。非同步編程允許程式在等待 I/O 操作完成的同時,繼續執行其他工作,提高響應速度和資源利用率。然而,非同步程式設計也增加了系統複雜性,需要謹慎處理錯誤和確保代碼可讀性。
Thumbnail
本文深入淺出地介紹日本新年傳統,包括年菜(おせち料理)的由來與食用時間、初日の出與ご來光差異、初夢與一富士二鷹三茄子吉夢、新年招呼用語「よいお年を」與「あけましておめでとうございます」的區別,以及相關節日日期。
Thumbnail
本文深入淺出地介紹日本新年傳統,包括年菜(おせち料理)的由來與食用時間、初日の出與ご來光差異、初夢與一富士二鷹三茄子吉夢、新年招呼用語「よいお年を」與「あけましておめでとうございます」的區別,以及相關節日日期。
Thumbnail
本文分享了荷蘭第一次所得稅申報的心得,重點在於BOX1的計算方式。介紹了報稅相關的基本情況,包括30% Ruling、雙薪家庭以及在荷蘭貸款購屋的情況,並提供了試算網站連結與基本的邏輯公式,幫助讀者瞭解如何估算應繳稅額及可扣除的項目,讓報稅過程變得更加清晰與簡便。
Thumbnail
本文分享了荷蘭第一次所得稅申報的心得,重點在於BOX1的計算方式。介紹了報稅相關的基本情況,包括30% Ruling、雙薪家庭以及在荷蘭貸款購屋的情況,並提供了試算網站連結與基本的邏輯公式,幫助讀者瞭解如何估算應繳稅額及可扣除的項目,讓報稅過程變得更加清晰與簡便。
Thumbnail
《腦筋急轉彎2》是一部涉及情緒和心理治療的電影,講述了青春期情緒的轉變和各種心理防衛機制。本文探討了電影中焦慮破壞大腦控制中心的情景、內在家庭系統(IFS)的心理治療方式以及因應策略等主題。
Thumbnail
《腦筋急轉彎2》是一部涉及情緒和心理治療的電影,講述了青春期情緒的轉變和各種心理防衛機制。本文探討了電影中焦慮破壞大腦控制中心的情景、內在家庭系統(IFS)的心理治療方式以及因應策略等主題。
Thumbnail
最近看了這本書,身為在健身房待過一段時間、又有一點點醫學常識的人,真心覺得這本書寫得很中肯,又一針見血地指出許多人在瘦身上的盲點。
Thumbnail
最近看了這本書,身為在健身房待過一段時間、又有一點點醫學常識的人,真心覺得這本書寫得很中肯,又一針見血地指出許多人在瘦身上的盲點。
Thumbnail
8月6日,No. 218,深入淺出 1. 不知道是不是所謂大道至簡,其實深入淺出本身就是功夫。 2. 傳遞知識不只是要讓聽眾覺得講師很厲害,還得讓聽眾覺得自己變得厲害了。 3. 有出一系列影片,每集用五個層次,從小孩到專家,介紹一個概念。一直覺得這系列蠻有趣的,可惜只有13集。
Thumbnail
8月6日,No. 218,深入淺出 1. 不知道是不是所謂大道至簡,其實深入淺出本身就是功夫。 2. 傳遞知識不只是要讓聽眾覺得講師很厲害,還得讓聽眾覺得自己變得厲害了。 3. 有出一系列影片,每集用五個層次,從小孩到專家,介紹一個概念。一直覺得這系列蠻有趣的,可惜只有13集。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News