ngx-worker-bridge

March 28, 2026 ยท View on GitHub

Offload CPU-heavy tasks and sync state across tabs using Web Workers โ€” with zero messaging boilerplate.

1. Why not just useState or zustand/redux?

These are great for UI state, but they have limitations:

  • Main Thread Blocking: Heavy computation (sorting, filtering large arrays, math) locks the UI thread, causing freezes and dropped frames.
  • N-Tab Isolation: Each tab runs its own store independently. Opening 5 tabs means 5 separate states with no awareness of each other.

2. Why not just manual Worker code?

Standard worker code is full of boilerplate plumbing:

  • Standard: You write postMessage, onmessage, manage request IDs for responses, handle SharedWorker ports manually, and build your own routing.
  • Library: You write a normal class and decorate a method. The bridge handles all messaging and cross-tab synchronization invisibly.

๐Ÿ—๏ธ The Problem it Solves

  1. CPU Offloading: Moves heavy computation off the 60FPS UI thread โ€” your app stays smooth even during expensive operations.
  2. Multi-Tab Sync: Synchronizes state (like counters, stock prices, or notifications) across all open browser tabs effortlessly via a SharedWorker.
  3. Connection Sharing: Shares a single WebSocket or polling connection across multiple tabs โ€” one connection, all tabs receive updates.

๐Ÿ› ๏ธ Key Concepts

1. Zero-Boilerplate Worker Calls

Standard worker code requires manual postMessage plumbing. This library uses the @RunInWorker() decorator and useWorkerStore hook to handle everything invisibly.

2. Cross-Tab Shared State

The "Killer Feature" is sharing a single background state across all open tabs using a SharedWorker.

Service Class (The Transmission):

import { RunInWorker } from 'ngx-worker-bridge';

class ApiService {
  @RunInWorker({ bridge: 'shared', namespace: 'analytics' })
  refreshData(): Promise<void> { return null as any; }
}

Module (The Engine โ€” runs in the worker):

import { setState } from 'ngx-worker-bridge';

export class AnalyticsModule {
  async refreshData() {
    const data = await fetch('/api/stats').then(r => r.json());
    setState('stats', data); // Broadcasts to ALL connected tabs instantly!
  }
}

Component (The View):

import { useWorkerStore } from 'ngx-worker-bridge/react';

const api = new ApiService();

function Dashboard() {
  const stats = useWorkerStore<any[]>('stats', 'shared');
  return <button onClick={() => api.refreshData()}>Stats: {stats?.length}</button>;
}

โš ๏ธ Requirements & Caveats

  • TypeScript Config: Add these to your tsconfig.app.json for decorator support:
    { "experimentalDecorators": true, "useDefineForClassFields": false }
    
  • rxjs required: Install it separately โ€” npm i ngx-worker-bridge rxjs. React does not include RxJS by default.
  • Naming Convention: The bridge strips "Module" from the class name to create the command namespace (e.g., AnalyticsModule โ†’ analytics). Override with @RunInWorker({ namespace: 'custom' }).
  • Same Origin: SharedWorkers require the main app and worker to be on the same domain.

๐Ÿ—๏ธ Setup Summary

  1. Define a Module: Your worker-side logic class.
  2. Define a Service: Your main-thread proxy class with @RunInWorker.
  3. Bootstrap: Call bootstrapWorker(...) in your entry point (main.tsx or App.tsx).
  4. Subscribe: Use useWorkerStore(key, bridgeName) in any component to reactively consume worker state.
  5. Debug: All worker console.log calls are automatically forwarded to your main console, prefixed with [Worker].