With Angular 2, we now have a number of event-related classes at our disposal. The EventEmitter handles all of our component output needs and the RxJS library handles, well, tons of other event-stream related functionality. As such, I thought it would be easy to leverage one of these classes in order to implement a Publish and Subscribe (pub/sub) services in Angular 2 Beta 14. But, after noodling on the problem for a while, I couldn't come up with anything that made particle sense. Both of these classes deal with binding and unbinding of event handlers; but, neither does it in a way that aligns well with an event mechanism intended to be consumed by Angular components. I don't want to abandon that idea, though. So, I thought I would to try and implement a simple pub/sub service for Angular 2; then, perhaps as a follow-up post, see how it could be refactored to use one of the existing event-oriented services.
I don't want it to sound like I am going against the EventEmitter or RxJS services. Just the opposite - they are both great services and provide loads of excellent functionality for an Angular 2 application. I'm only saying that for a very specific type of pub/sub use-case, neither of them seems to fit the bill.
When I think about pub/sub in an Angular application, I think about it in the context of the component life-cycle. Meaning, when a component is instantiated, I need to bind a number of event handlers; then, when the component is destroyed, I need to unbind all of those same event handlers in order to avoid memory leaks and unpredictable behavior. Both of these actions should be easy; and, I'd like a publish and subscribe service that implements methods that align with this requirement.
In AngularJS 1.x, I would sometimes use the $scope tree as a make-shift publish and subscribe service. While not the most efficient approach, it certainly made event binding incredibly easy. In fact, when using the $scope tree, you never even needed to unbind your event handlers as they were automatically unbound when the $scope [associated with your component] was destroyed.
In Angular 2 Beta 14, we no longer have a $scope tree. But, I'd like to create a service that still affords that kind of ease-of-use. My two main goals would be bulk or fluent event binding and bulk unbinding. This way, no matter how many event handlers I bind within an Angular 2 component, I want to be able to unbind all of them without the chance of forgetting any.
To accomplish this, I came up with a service that has your traditional on/off methods. But, that also allows you to partially apply those methods to a specific context:
// Bind the API to the current component context. This will implicitly associate // each event handler with the current context. Not only does this apply to the // invocation of the event handlers - it also allows for mass unbinding. var boundAPI = pubsub.bind( this ) .on( "eventA", handlerA ) .on( "eventB", handlerB ) .on( "eventC", handlerC ) ; boundAPI.on( "eventD", handlerD ); boundAPI.on( "eventE", handlerE );
Notice that I am calling .bind( context ) before I call any of the .on() methods. The .bind() method returns a version of the API that is partially applied for the given context. Meaning, it will implicitly use the given context object when invoking the various .on() and .off() methods.
On the other end of the component life-cycle, this .bind() approach allows for a simple unbinding of all event handlers bound to the current context:
// Using the API returned by the .bind() method. Unbind all event handlers that // were bound though the boundAPI. boundAPI.unbind(); // ... or, using the core pubsub API with an explicit context. pubsub.unbind( this );
As you can see, unbinding event handlers becomes super simple since it no longer has to mirror the set of bound event handlers. Meaning, you never have to change your .unbind() statement even as you continue to evolve the set of bound event handlers within your Angular 2 component. This is the kind of ease-of-use that I'm looking for.
Let's take a look at this PubSub service in the context of a simple Angular 2 Beta 14 application. In the following code, I can add and remove child components from the active application. Each of these child components uses the PubSub service to both trigger and subscribe to events. As child components are added and removed, we can see that the event bindings are easily managed.
As you can see, each PubChild component uses the .bind() method when setting up its event handlers. Then, in the ngOnDestroy() component life-cycle event, it simply calls .unbind(). In doing so, all if its bound event handlers are correctly cleaned up. Because of this, we can see that, as PubChild components are added and removed, the list of console log statements accurately reflects the list of active components:
I'm sure that there are a ton of existing Pub/Sub style libraries out there already. So, it may seem silly or even foolish to try and create one yourself. But, I really wanted a publish and subscribe API that aligned well with the Angular 2 component life-cycle. And, since publish and subscribe code doesn't have to be overly complicated, it can be worth it to write something from scratch that perfectly suits your needs.
Want to use code from this post? Check out the license.