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
- CPU Offloading: Moves heavy computation off the 60FPS UI thread โ your app stays smooth even during expensive operations.
- Multi-Tab Sync: Synchronizes state (like counters, stock prices, or notifications) across all open browser tabs effortlessly via a SharedWorker.
- 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.jsonfor decorator support:{ "experimentalDecorators": true, "useDefineForClassFields": false } rxjsrequired: 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
- Define a Module: Your worker-side logic class.
- Define a Service: Your main-thread proxy class with
@RunInWorker. - Bootstrap: Call
bootstrapWorker(...)in your entry point (main.tsxorApp.tsx). - Subscribe: Use
useWorkerStore(key, bridgeName)in any component to reactively consume worker state. - Debug: All worker
console.logcalls are automatically forwarded to your main console, prefixed with[Worker].