An Angular Messenger

The component nature of Angular means that it is imperative that we pay attention to the communication strategy we use for components to talk to one another. Communication between components is a primary place for problems to arise in code. A poor plan for communication can lead to things like using global variables in order to communicate and pass data between components.

So what is a good communication strategy for components? I am not going to pretend to have a silver bullet answer. There are usually multiple correct ways to do things. But in general, parent components communicate with child components by setting Inputs or calling public methods on the child component. Initial communication to a child component can be accomplished by passing data to the component through its Inputs. More complex communication or interaction can occur by creating a component reference in the parent to the child and accessing its public members like public methods or properties.  Child components can communicate to their parents utilizing events, or more specifically, EventEmitters, to notify the parent that something of interest has occurred and pass data about the interesting occurrence.

What about sibling components? How should sibling components communicate with each other. One method is through a shared parent, but this is really just a modified parent child communication strategy. One child will emit an event to the shared parent, which will in turn pass data to the second child via some property or method.  Another method sibling components can use to communicate is through shared services. A service, injected into both components, can facilitate responding to events or method calls from one component and emitting corresponding events to the other component. One negative of shared services is that it can lead to these shared services maintaining state.  In order to facilitate “passing the data from one component to another”, the service becomes stateful. I prefer to keep my service classes stateless, which is a good practice for many components as well.

A better solution is to introduce a “messenger” which follows a publish-subscribe pattern. Any component can publish a “message” and any other component can subscribe to that message. This is a common and popular pattern and there are multiple examples of messenger implementations for Angular. Some of these examples allow you to subscribe to certain messages by some unique ID. Others use strings to identify the unique messages that can be subscribed to. When I was learning Angular, I had the need for a messenger. While there were other messenger examples out there that worked perfectly well, I wanted to avoid using IDs or strings. I also wanted to take the opportunity to make my messenger a little more type-safe, taking full advantage of Typescript.

My criteria for the messenger were:

  • The messenger should use the actual message class as the key, instead of an ID or a string
  • The messenger should auto register messages that can be subscribed to
  • The messenger should be accessed in a fluent manner
  • Subscribing to a message should use a familiar convention

Below is the code for a messenger component that fulfills these criteria.

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

class MessageHandler {
 key: any;
 handler: Subject<any>;
}

export interface IMessage {}

@Injectable()
export class MessengerService {
 private _registry: Array<MessageHandler> = new Array<MessageHandler>();

 public For<T extends IMessage>(iMessage: Type<T>): Subject<T> {
 let messageObserver = this._registry.find(entry=>(<any>entry.key).name == iMessage.name);
 if(!messageObserver){
 messageObserver = { key: iMessage, handler: new Subject<T>() };
 this._registry.push(messageObserver);
 }
 return messageObserver.handler as Subject<T>;
 }

}

Since our messenger should be able to support any type of message, in order to get the type-safety that we want, we need to be able to specify some base class or interface. In this case a marker interface is used, that is, an interface that does not enforce any specific contract on its implementing class. We also need to be able to store the types of messages that can be subscribed to so they can be referenced later. In order to accommodate this, a simple registry was used which is simply an array of MessageHandlers. Each MessageHandler in the registry uses an actual message type, or at least the name of the message Type, as the key and a Subject from the Reactive Extensions library as a proxy to an Observable. Developers using Angular will be familiar with subscribing to an Observable, so this will be a common convention for responding to published messages.

In this example, the messenger consists of a single function, For.  This will allow more fluency in its usage as we’ll see later. In the generic function, T is constrained to be a Type that extends IMessage. A Type of IMessage is passed as an argument to the function and this is the same Type that will be the expected value of the returned Subject. The name of the Type is used to lookup the MessageHandler entry in the registry. If there is not an entry for that particular Type, one is added.  This facilitates the self registration of the message types in the registry. Finally, the handler from the MessageHandler registry entry, which is a Subject that returns the Type requested, is returned to the caller. Once the caller has access to the Subject, the calling code can use it just like any other Observable. Calling code can push new instances of the message type to it by calling “next”, “subscribe”, or “unsubscribe” just as with any other Observable.  Usage of the messenger can be seen below.  Given a class that implements IMessage.

class TestMessage implements IMessage{
   messageText: string;
}

The messenger can be used to publish and subscribe to an instance of TestMessage as shown below.

...
this.messageSub = this._messenger.For(TestMessage).subscribe((message)=>{
   console.log("MESSAGE RECEIVED: " + message.messageText);
})

this._messenger.For(TestMessage).next({testMessage: "hello"});
...

The first time For is called for the type of message TestMessage, an entry is made in the registry. It doesn’t matter whether the first call is used to subscribe or publish a message.  A subscription is added to the Observable and will receive any instances of TestMessage published through the messenger. Above you see a subscription and a published message of type TestMessage.

This particular implementation of a messenger utilizes Typescript’s type-safety to avoid “magic strings and IDs” which can cause problems when the strings are misspelled or the wrong ID is used for a particular message. Since I should always know what message I am interested in subscribing to, I like being able to use the Type of that message to reference the subscription.

Leave a Reply

Your email address will not be published. Required fields are marked *