The project I am currently working on required a mechanism how unrelated components can communicate. As a simple example, in our application an administrator may edit any user in an administrative section. If he/she edits her own user, the top level menu should reflect the change immediately, without a page refresh. The user section and the top menu bar are unrelated, so the top level menu bar should not have knowledge of the administrative section. While there are some differing opinions on the pattern, the Message Bus pattern seems to be well suited solve this problem.
We wrote a simple implementation of this pattern for Angular 2 named
UPDATE 1: The implementation suggested below breaks with mangling. It’s not a problem if you aren’t planning to mangle, but you probably would want to at some point. This implementation requires more thought. Caveat emptor.
The Message Bus (also called Event Bus) is a publish / subscribe pattern. It involves a central service, the Message Bus, to which messages are published, and interested parties can use the Message Bus to subscribe to be notified of published messages. This allows different parts of the application to communicate without having direct knowledge of each other.
Typically, interested parties are only interested in a subset of all published messages. In other words, messages should be filtered.
Implementation and usage in Angular 2 and TypeScript
There are a few ways to implement the general pattern. Our implementation was inspired by the MVVM Light framework, which was used by some of our previous projects. One notable detail of the MVVM Light implementation are that messages are filtered by the type (class) of the message.
- Ease of use
- Strong typing of messages
- Leverage facilities provided by Angular 2
Before looking at the implementation, it’s useful to consider how the service will be used by clients. Indeed, I’ve found that thinking about the callers first, i.e. how the new service will be used, will result in a better design.
Typical use cases are described below. Implementation notes and full code of the
MessagingService can be found after the use cases.
- Create a class for the message. For instance:
The class defines the structure of the message - it can be anything - whatever the publisher needs to communicate or whatever the subscribers need to do their job. In the case of
User entity updates, the entire updated object is passed in the message.
MessagingService.publish()and provide the message instance as the parameter.
Usually, the service instance (
this.messaging in the example above) should be injected by Angular’s dependency injection facilities.
It was tempting to create a generic message class, as it seems like a hassle to create a message class for each new type of message. However, with a generic message class we would lose compiler type checking on message, and we’d have to manage message filtering. The filtering would need to be by some
type field - values of which would either be (a) magic strings (b) constants or (c) enum members. In all cases, we still need to manage some new values for each message type and creating a class doesn’t seem such a big overhead anymore.
Subscribing to messages
- Obtain an
Observableinstance of published messages by calling
messagingService.of()and passing in the message type. Subscribe to the observable and save a reference to the subscription, for instance.
- Clean up. If the component is destroyed, the subscriptions should be disposed of in the
The application may leak memory and unwanted side effects may occur if the
Subscription is not disposed when the component is destroyed, since the service would keep a reference to the subscription, therefore the objects referenced in the subsciption cannot be garbage collected.
Since Angular 2 uses rxjs it was an obvious choice for the internal implementation of the message stream. The message stream is a simple
Subject, of which filtered
Observables<T> are exposed to the outside world. The topic of the message is the message class itself, or more specifically the class name.
Internally the messages are wrapped in a
Message object and the filtering is done based on the provided type name. Using the message type as the channel avoids the use of untyped strings or additional enums for message filtering, and allows tools to provide code suggestions/intellisense for subscription callbacks.
Code for the
Comments are welcome.