An updated MessageBus Service for Angular

I am beginning to learn Angular (not AngularJS / old Angular, but the new hotness), and came across the need to communicate a simple status from one component to another. In most other component-based UI programming paradigms, the thing to do here is to use a Message Bus service to communicate between components. The advantage is low coupling; components don’t need to know about each other, they just need to know about this thing called a Message Bus.

I did some digging, and came across this old blog post about how to build a message bus service in Angular. Unfortunately, copying the code into my IDE gave me an error about a missing filter method…  Alas. After fiddling for a while, here’s what I came up with instead:

import { Injectable } from '@angular/core';

import {Observable, Subject, Subscription} from 'rxjs';

import {filter} from 'rxjs/operators';



interface Message {

  channel: string;
  data: any;

}



@Injectable({
  providedIn: 'root'
})

export class MessagingService {



  private message$: Subject<Message&gt;;



  constructor() {

    this.message$ = new Subject<Message&gt;();

  }



  public publish<T&gt;(message: T): void {

    const channel = (<any&gt;message.constructor).name;

    this.message$.next({ channel: channel, data: message });

  }



  public subscribe<T&gt;(messageType: { new(...args: any[]): T }, subscriber: (value: T) =&gt; void): Subscription {

    const channel = (<any&gt;messageType).name;

    const filtered = this.message$.subscribe(m =&gt; {

      if (m.channel === channel) {

        subscriber(m.data);

      }

    });

    return filtered;

  }

}


Now I recognize that there are quite a few issues with this implementation. Not the least of which is that each subscriber gets every message, and then has to do string comparison on a payload field to determine if the message is one that they are interested in. After reworking it, I came up with a much more CPU-efficient implementation:

import { Injectable } from '@angular/core';
import {Subject, Subscription} from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class MessagingService {

  private subjects$: Map<string, Subject<any&gt;&gt;;

  constructor() {
    this.subjects$ = new Map<string, Subject<any&gt;&gt;();
  }

  private getOrCreateChannel(channelName) {
    let channel = this.subjects$.get(channelName);
    if (channel === undefined) {
      channel = new Subject<any&gt;();
      this.subjects$.set(channelName, channel);
    }
    return channel;
  }

  public publish<T&gt;(message: T): void {
    const channelName = (<any&gt;message.constructor).name;
    const channel = this.getOrCreateChannel(channelName);
    channel.next(message);
  }

  /**
   * A more efficient publish method, for cases where a publisher repeatedly produces values on the same topic
   * @param messageType the type of message that we expect tp publish
   */
  public getPublisher<T&gt;(messageType: { new(...args: any[]): T }): (T) =&gt; void {
    return this.getOrCreateChannel((<any&gt;messageType).name).next;
  }

  public subscribe<T&gt;(messageType: { new(...args: any[]): T }, subscriber: (value: T) =&gt; void): Subscription {
    const channelName = (<any&gt;messageType).name;
    const channel = this.getOrCreateChannel(channelName);
    return channel.subscribe(subscriber);
  }
}
%d bloggers like this:
search previous next tag category expand menu location phone mail time cart zoom edit close