~ About 5 minutes read.

~

Alright, Davide, what the hell is pubsub?

I see you are eager to learn, perfect.

Just get to the point.

Straightforward as always.

~

You just met Gustavo. He's going to ask the questions today.

PubSub be it

Pubsub is a pattern, not only in Angular or in Javascript, but in programming in general. It stands for publish-subscribe, and it's achieved via a communication between a publisher and a subscriber. The subscriber decides to subscribe to an event queue, and every time the publisher provides an update, every listener subscribed to that queue receives it.
PubSub is then a way to get notified about updates to whatever resource the publisher is sharing. It works much like an event listener, where the publisher is the DOM and the subscriber is our thread.
When we write

  document.getElementById('foo').addEventListener('click', () => {
    // do magic...
});

we are saying: "Yo, Mr. foo element, can you please tell me whenever someone clicks you? You will? And when you do, can you also call this function I'm providing you? Great! Always a pleasure to do business with you".

~

I'm sure that's exactly how that happens.

Shut up.

~

Pubsub works exactly the same way, and what's more is that you are in charge of the nature of the events published and thus they don't need to be triggered by an interaction with the DOM.

~

Oh, I got it! It's like with $on and $emit or $broadcast.

That's correct. Turns out you actually think sometimes.

Seems you don't do the same though. If we have $on and $broadcast at our disposal why would we bother trying implement something else?

Finally one smart question.

~

Sure $on and $broadcast are much easier to plug into your application, and they also are a way to implement a pubsub pattern, but I found them to be very confusing as to why they may not work as expected. We'll have to digress a bit here.

Why not $on and $broadcast

First of all, let's get back to the basics for a second and explain how these two work.
In Angular we can listen to a custom event in these two ways, both to be used inside a controller:

  $scope.$on('eventName', (event, data) => {
    // do something useful
});

$rootScope.$on('eventName', (event, data) => {
    // do something even more useful
});

We'll get to the difference between the two in a minute.
To publish an event we also have two options:

  $scope.$broadcast('eventName', { content: 'Something interesting' });

$scope.$emit('eventName', { content: 'Something fresh' });

They both can be used with the rootScope, but they differ in the fact that $emit sends an event upwards the scope chain, and $broadcast sends it down. What they do have in common is that they can only reach the first parent/child scope, and not their siblings. There's an easy fix for that, but it doesn't look like the right way:

  $scope.$parent.$broadcast('eventName', { content: 'I can reach my siblings!' });

We could also use the $rootScope, as it doesn't have the immediate parent limitation, just remember that if we use $rootScope.$emit the event we send will only be catched by $rootScope.$on listeners, while the $broadcast will target both $rootScope and $scope.
Confused yet? That's what I'm talking about. It shouldn't have to be this way.
Luckily there are people like Todd Motto that write incredibly clear articles on the subject so that we can calm down at the sight of the $on, $broadcast and $emit crew.
On a side note, I'm not saying that you shouldn't use $on and $broadcast. I'm just saying that I don't want to figure out if I can reach the recipients, and how I have to go about that, every time I need to propagate an event.
That leads exactly to the reasons I prefer a custom implementation of the pubsub.

Why custom pubsub then

With a custom implementation all the pieces that make up the communication between the publisher and the subscriber are available, because you know exactly where the event was generated and who published it. Moreover, you are 100% sure that the subscribers callback function will be called, because there is nothing for the app to figure out, like where the caller scope is and stuff like that.

~

Yeah, alright, great. Can you PLEASE show me some code now? I'm dying.

...

~

The Angular way

First of all, we need a service that provides an API to subscribe to some events. Let's say we want to fetch some tweets from my Twitter profile and trigger an 'updatedTweets' event only if I tweeted something today.

~

Pretty useful.

Yeah, like you.

~

  
angular.module('pubSub', [])

.factory('newTweets', () => {

    // we provide an object with the events available for subscription
    // each array holds the functions to call when we publish 
    // the event it's related to
    const events = {
        'updatedTweets': [],
        'updatedPosts': []
    };

    // we allow the controller to subscribe 
    // to an event, and call a function when
    // it's triggered
    const on = (evName, cb) => {
        // we make sure the event passed exists
        if( Objects.keys(events).includes(evName) ){
            // if it does we add the callback to the 
            // corresponding event queue
            events[evName].push( cb );
        } else {
            // if not, we let the user know
            console.info(`Cannot subscribe to unknown ${ evName } event`);
        }
    };

    // this function allows to trigger an event
    // the args parameter is what we are going to pass
    // the controller that subscribed to the event
    const publish = (evName, args) => {
        // we again make sure the passed event exists
        if( Objects.keys(events).includes(evName) ){
            // if it does we call every callback function bound to this event 
            events[evName].forEach(event => event( args ));
        } else {
            // if not we let the user know
            console.info(`Cannot publish unknown ${ evName } event`);
        }
    };

    // we fetch my latest tweets
    const fetch = () => {
        // let's pretend we get the timestamp we need
        const todayAtMidnight = getTodayAtMidnightTimeStamp();
        // and that we synchronously get all my tweets
        const tweets = getTweets();
        // and that the tweet date is stored as a timestamp
        const todayTweets = tweets.filter( tweet => tweet.date > todayAtMidnight  );
        if( todayTweets.length > 0 ){
            // Hooray! We found tweets, let's tell the world!
            publish('updatedTweets', todayTweets);
        }
    };

    return { on, fetch }

});

And on a controller we subscribe to the event we want.

  angular.module('main', ['pubSub'])

.controller('mainCtrl', ($scope, newTweets) => {

    $scope.tweets = newTweets.fetch();

    newTweets.on('updatedTweets', newTweets => {
        console.log('The world is now safe');
        console.info('Check them tweets out', newTweets);
    });

});

And that's all it is. Better? You decide, for me it sure is :)

More reading

There are tons of people smarter than me who talked a bit about pubsub. Here's a list of great resources for you:



~

Are we done ?

Yes, you can go grab a beer with your friends.

Cool, I love you.

I know.

~


807 0 5