notes

Log | Files | Refs | README

singleton_channels.md (2634B)


      1 # Singleton Channel
      2 
      3 ```
      4 Singleton Channel (neutral in-app bus)
      5 -----------------------------------------
      6 
      7    Publishers                          Subscribers
      8    ----------                          -----------
      9    +--------+                          +--------+
     10    | View A |----+                 +-->| View C |
     11    +--------+    |                 |   +--------+
     12                  v                 |
     13             +---------------------------+
     14             |     AppChannel<Event>     |  (singleton)
     15             +---------------------------+
     16                  ^                 |
     17    +--------+    |                 |   +--------+
     18    | Store  |----+                 +-->| Logger |
     19    +--------+                          +--------+
     20 ```
     21 
     22 A `Channel<T>` is a globally shared, typed event bus. Any component can publish
     23 a value of type T into it, and any component that has subscribed will receive
     24 that value — FIFO (first subscriber registered, first notified). It's simpler
     25 than a full store because it carries no persistent state; it just broadcasts a
     26 moment-in-time event.
     27 
     28 ```ts
     29 class Channel<T> {
     30   private subscribers: Array<(data: T) => void> = [];
     31 
     32   subscribe(cb: (data: T) => void): () => void {
     33     this.subscribers.push(cb);
     34     return () => {
     35       this.subscribers = this.subscribers.filter((s) => s !== cb);
     36     };
     37   }
     38 
     39   publish(data: T): void {
     40     for (const cb of this.subscribers) {
     41       cb(data);
     42     }
     43   }
     44 }
     45 ```
     46 
     47 Because it's generic, TypeScript enforces that every publisher and subscriber
     48 agrees on the shape of `T` at compile time — you can't accidentally publish a
     49 `string` on a `Channel<UserEvent>`.
     50 
     51 ## Defining Global Singleton Channels
     52 
     53 You declare each channel once and export it as a module-level singleton:
     54 
     55 ```ts
     56 // channels.ts
     57 interface UserLoggedIn {
     58   userId: string;
     59   role: "admin" | "user";
     60 }
     61 interface CartUpdated {
     62   itemCount: number;
     63   total: number;
     64 }
     65 
     66 export const userLoginChannel = new Channel<UserLoggedIn>();
     67 export const cartChannel = new Channel<CartUpdated>();
     68 ```
     69 
     70 Any file can import and use these without passing them through props or
     71 constructors.
     72 
     73 ## Usage Across Components
     74 
     75 ```ts
     76 // ComponentA.ts — subscriber
     77 import { cartChannel } from './channels';
     78 
     79 const unsub = cartChannel.subscribe(({ itemCount, total }) => {
     80   document.getElementById('cart-count')!.textContent = String(itemCount);
     81 });
     82 
     83 // call unsub() when component is destroyed to avoid memory leaks
     84 
     85 // ComponentB.ts — publisher
     86 import { cartChannel } from './channels';
     87 
     88 function addToCart(item: Item) {
     89   // ... update cart logic
     90   cartChannel.publish({ itemCount: cart.length, total: cart.reduce(...) });
     91 }
     92 ```