DEV Community

Cover image for Stop Writing postMessage Manually For Workers — I Built a Decorator for That
Yashwant Kumar N T
Yashwant Kumar N T

Posted on

Stop Writing postMessage Manually For Workers — I Built a Decorator for That

Stop Writing postMessage Manually For Workers — I Built a Decorator for That

Tags: angular, react, webdev, javascript


Web Workers are one of the most underused features in modern web development. They let you run heavy JavaScript off the main thread — keeping your UI smooth and responsive.

But the API is painful:

// Standard Worker code — just to call ONE function
const worker = new Worker('./my.worker.js');
const requestId = Math.random();

worker.postMessage({ id: requestId, command: 'processData', payload: data });

worker.onmessage = (event) => {
  if (event.data.id === requestId) {
    console.log(event.data.result);
  }
};
Enter fullscreen mode Exit fullscreen mode

You need request IDs, response matching, manual routing, port management for SharedWorkers... for every single method call.

So I built ngx-worker-bridge to eliminate all of that.


What it looks like instead

Define your background logic (runs in the worker thread):

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

export class DataModule {
  private count = 0;

  increment() {
    this.count++;
    setState('counter', this.count); // broadcasts to ALL tabs
  }

  async processData(payload: any) {
    // heavy work here — UI thread never blocks
    return expensiveOperation(payload);
  }
}
Enter fullscreen mode Exit fullscreen mode

Call it from Angular like a normal service:

import { Injectable } from '@angular/core';
import { RunInWorker, workerStore } from 'ngx-worker-bridge';

@Injectable({ providedIn: 'root' })
export class DataService {
  count$ = workerStore<number>('counter', 'shared'); // reactive, auto-updates

  @RunInWorker({ bridge: 'shared', namespace: 'data' })
  increment(): Promise<void> { return null as any; }

  @RunInWorker({ namespace: 'data' })
  processData(payload: any): Promise<any> { return null as any; }
}
Enter fullscreen mode Exit fullscreen mode

Or from React with a hook:

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

function App() {
  const count = useWorkerStore<number>('counter', 'shared');
  return <button onClick={() => service.increment()}>Count: {count}</button>;
}
Enter fullscreen mode Exit fullscreen mode

No postMessage. No onmessage. No request ID tracking. Just a decorator and a hook.


The SharedWorker feature

The real power comes from SharedWorker support. A SharedWorker runs once and is shared across all open browser tabs. When you call setState('counter', value) inside your module, it broadcasts to every connected tab instantly.

Open 5 tabs — they all stay in sync. Without a server. Without WebSockets.

This is useful for:

  • Notification counts
  • Live stock/crypto prices
  • Shared timers or sessions
  • One WebSocket connection shared across all tabs

Install

# Angular (RxJS already included)
npm i ngx-worker-bridge

# React
npm i ngx-worker-bridge rxjs
Enter fullscreen mode Exit fullscreen mode

Would love feedback — especially from anyone who's dealt with SharedWorker pain before.

Top comments (0)