Rick's DevNotes
筆記關於我作品集
筆記類別
  • 全部
  • DockerDocker
  • NetworkNetwork
  • RxJSRxJS
  • NginxNginx
  • TypeScriptTypeScript
  • Data_Structure_And_AlgorithmData Structure And Algorithm
  • JavaScriptJavaScript
  • PostgreSQLPostgreSQL
  • ReactReact
  • GitGit

© 2026 Rick's DevNotes. All rights reserved.

# Observable# Stream

建立時間:2022/10/03

Subjects

在這章中,我們來談談 RxJS 的另一個實用概念:Subjects。Subject 可以讓我們將值 multicast 給多個 Observers。它類似事件發射器 (event emitter),允許新增或移除監聽器,並將事件廣播給所有訂閱者。

最大的特色是:Subject 同時具備 Observable 與 Observer 的特性。這代表我們既可以像訂閱 Observable 一樣訂閱 Subject,也可以呼叫它的 next、error、complete 方法來廣播通知。

Subject 的運作方式

與一般的 Observable 不同,Subject 是 Hot Observable。換句話說,Subject 本身就是共享的資料來源,所有訂閱者都會同時收到它發送的值。

以下用一個比喻來理解:Subject 就像一台播放比賽的電視機 (TV),任何觀眾只要開始收看,就能立刻接收到比賽中的最新事件(例如進球、犯規)。

想像電視正在播放一場比賽,這台電視就像一個 Subject,可能會發出很多事件:進球得分、球員受傷等等,這些事件就像 next 通知。

  • 沒有人在看電視 → 沒有任何反應。
  • 觀眾(Observers)開始訂閱 → 當比賽中發生進球事件時,所有觀眾同時收到這個通知。

這就是 MultiCasting 的概念,同一個 Subject 的通知,可以同時傳遞給應用程式中不同位置的多個 Observer。Subject 本身也提供了 next、error、complete 方法。這表示我們可以在程式的任何地方,直接呼叫 subject.next(value),Subject 就會把這個通知 multicast 給所有目前已訂閱的 Observers。

NOTE: Subject 就像 RxJS 裡的 event emitter,能與其他 Observables 結合。

時間軸示意

下面用簡單的步驟來看 Subject 的時間軸:

  1. 執行 next(A)
    • Subject 發出 A。
    • 這時還沒有人訂閱,所以沒人收到任何東西。
  2. 有第一個人訂閱後,執行 next(B)
    • Subject 發出 B。
    • 這位訂閱者馬上收到 B。
  3. 又有第二個人訂閱後,執行 next(C)
    • Subject 發出 C。
    • 這時所有已經訂閱的人都會同時收到 C。

另外,Subject 也可以呼叫:

  • complete() → 結束目前所有的訂閱。
  • error() → 讓所有訂閱收到錯誤訊息並結束。

NOTE: Subject 會在呼叫 next、error、complete 時,把通知 multicast 給當下所有的訂閱者。

Subject 程式範例

用一個範例來示範 Subject 如何將值 multicast (multicast) 給所有有效的訂閱者。

畫面上有一個輸入框,讓我們輸入文字,並透過 Emit 按鈕 來發送輸入的值。 同時還有一個 Subscribe 按鈕,每點擊一次就會新增一個新的訂閱者,讓他能接收 Subject 傳送的值。

建立 Subject

我們先建立一個 Subject,用來發送字串型別的值:

Emit 按鈕邏輯

監聽 Emit 按鈕的點擊事件,並透過 Subject 的 next 方法發送輸入框的值:

Subscribe 按鈕邏輯

監聽 Subscribe 按鈕的點擊事件,每次點擊都新增一個新的訂閱者,並在收到值時輸出到 console:

執行流程

  1. 如果輸入「A」並點擊 Emit → 因為沒有訂閱者 → 不會有任何輸出。
  2. 點擊一次 Subscribe → 新增一個訂閱者。
    • 接著輸入「B」並點擊 Emit → 該訂閱者會收到「B」。
  3. 再次點擊 Subscribe → 現在共有兩個訂閱者。
    • 輸入「C」並點擊 Emit → 兩個訂閱者同時收到「C」。

Subject 也是 Observer

文章開頭有提到,Subject 不只是 Observable,也是 Observer。因此我們能用一個小技巧:我們可以使用 pipe 搭配 map,把點擊事件轉換成輸入框的值,然後直接把 value$ 當作 subscribe 的參數

如此一來,Observable 發出的每個通知,都會透過 value$ 被 multicast 給所有訂閱者。


BehaviorSubject

與 Subject 的差異

BehaviorSubject 是 Subject 的進階版本,它在建立時需要一個初始值,並且會記住最新一次的值。

先回顧一下之前的例子:我們用 Subject 來 multicast 比賽中的進球事件。

  • 如果有兩個觀眾(Observers)從比賽一開始就訂閱,他們可以一路計分,始終知道比數。
  • 但如果在中途加入一個新觀眾,他就不知道目前的比數,因為 Subject 只會把之後的事件傳給訂閱者。

然而,很多情境下,我們並不只是關心「單一事件」,而是要掌握「狀態」,就像我們不只是想知道進球瞬間,而是想知道當下的比分。因此,我們可以改變策略:

  • 每次比分變動時,不是發送「進球事件」,而是發送「最新比分」。
  • 這樣即使新觀眾中途加入,他也能在比分更新時獲得當下的正確狀態。

在這種情況下,解決方案就是 BehaviorSubject。它的運作方式就像把比分顯示在螢幕上一樣。每當有新的觀察者(Observer)加入時,它會立即接收到一個 next 通知,其中包含最新的值。這樣,新觀察者就能立刻知道比賽的最新狀態,並有資料可以反應。在初次傳送最新值之後,BehaviorSubject 的行為就和一般 Subject 相同。換句話說,BehaviorSubject 會將最新發送的值儲存在記憶中,並在有新的訂閱建立時,立刻將這個值發送給新訂閱者。

要注意的是,使用 BehaviorSubject 時,仍需要注意一些問題:

  • 新觀眾在剛訂閱時,必須等待下一次比分更新才有資料,
  • 在那之前,他會處於「沒有任何資訊」的狀態。

時間軸示意

如下方示意圖,我們有一條時間軸,BehaviorSubject 會將更新後的比分 multicast 給所有訂閱者。

BehaviorSubject 和一般 Subject 最大的不同,就是它一定要有一個初始值。比如我們設定初始比分是 0-0。當有訂閱者加入時,他會馬上收到這個初始值,也就是 0-0,所以一開始就能顯示正確的比分。

接著,假設比賽進球了,我們用 next 方法傳入新的比分,這個新比分會立刻發送給所有目前的訂閱者(假設現在只有一個)。如果這時又有新的訂閱者加入,他也會馬上收到最新的比分(例如 1-0),不會錯過任何狀態。

如果比賽又進球,我們再用 next 傳入新的比分,這時所有訂閱者(不管是早加入還是晚加入)都會同時收到最新的比分。總結來說,BehaviorSubject 跟一般 Subject 很像,但多了「初始值」和「記住最新值」的功能,非常適合用來保存和同步「狀態」。

  • 一般 Subject:新訂閱者只能收到「之後」發送的值。
  • BehaviorSubject:新訂閱者一加入就能馬上拿到目前的最新值。

程式範例:登入狀態管理

使用 Subject

在上面的範例中,如果我們一開始就點「Print state」按鈕,會發現沒有任何反應。這是因為:

  • withLatestFrom 需要每個來源都至少發出過一次值,才會把值組合起來往下傳。
  • 一開始 isLoggedIn$ 還沒呼叫過 next(),所以 click 事件拿不到「最新狀態」,整個流程就不會繼續,也看不到 console.log() 的結果。
  • 只要我們先按一次「Login / Logout」讓 isLoggedIn$ 有值,之後每次點「Print state」都能正確拿到最新狀態。

使用 BehaviorSubject

不管什麼時候,只要點「Print state」按鈕都會有結果:

  • BehaviorSubject(false) 一建立就會先發出初始值 false,並把它記住。
  • withLatestFrom 在第一次點擊時就能拿到這個值,組成 [clickEvent, false],所以 console.log() 會顯示 false。

總結

  • Subject:可作為事件廣播器,支援 multicast。
  • BehaviorSubject:在 Subject 的基礎上,提供 初始值 與 最新狀態快取。
  • 使用時機:
    • Subject:適合用於即時事件廣播。
    • BehaviorSubject:適合用於需要記住狀態的情境,如登入狀態、應用程式設定等。

參考資料

  • RxJs
  • RxJs In Practice
  • RxJS 7 and Observables: Introduction