Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save supertinou/4a17da1ff992f77b58ca8a2a9269fd07 to your computer and use it in GitHub Desktop.
Save supertinou/4a17da1ff992f77b58ca8a2a9269fd07 to your computer and use it in GitHub Desktop.
How to create an AngularJS Message Factory from scratch with PubNub's AngularJS SDK

How to create an AngularJS Message Factory from scratch with PubNub's AngularJS SDK

The PubNub AngularJS SDK v 3.2.1 come out of the box with a $pubnubChannel object that abstracts all the steps needed to communicate with PubNub directely.

It allows to interact with PubNub Data Stream Network in a seemless way:

.controller('ScoresCtrl', function($scope, $pubnubChannel) {

  $scope.scores = $pubnubChannel('game-scores-channel',{ autoload: 20 })
  $scope.score.$publish({player: 'John', result: 32}) // Publish a message in the game-scores-channel channel.
});

Instantiating the $pubnubChannel is the only step needed to have a scope variable that reflects the realtine data from a channel. It subscribes to the channel for you, load initial data if needed and receive new realtime data automatically.

However this abstraction layer hide the direct interaction with the PubNub API and you may want to understand how it works under the hood.


This tutorial will teach you how to create a Message Factory from scratch which will be responsible for storing the messages and communicating directly with PubNub to retrieve them in real-time.

message-factory

The Message factory will store the messages in an array and expose 5 functions to the outside:

Method Role
getMessages() Getting the messages
sendMessage(string: messageContent) Sending a message
subscribeNewMessage(function: callback) Being notified of new messages
fetchPreviousMessages() Fetching the previous messages
messagesAllFetched() Indicates if all the messages have been fetched or not

Furthermore, this factory will emit the event factory:message:populated to notify that the factory has been populated with the previous messages.

This message factory will be standalone. This means that you can inject it anywhere in your app, in your controllers, and directives in order to interact with it.

The code of this service is available in this gist

The source code is well commented enough so you can understand easily how it is working under the hood.

Building the Message factory

Let’s create the base of our message factory which we will be implementing step-by-step.

→ Create a base of our factory that will be storing an array of messages, keep track of the PubNub channel we synchronise the data with, save the timestamp of the first message stored and expose useful functions to interact with it from the outside.

The code should looks something like this:

angular.module('app')
.factory('MessageService', ['$rootScope', '$q', 'Pubnub', 'currentUser',
    function MessageServiceFactory($rootScope, $q, Pubnub, currentUser) {
        // Aliasing this by self so we can access to this in the inner functions
        var self = this;
        this.messages = []
        this.channel = 'messages-channel';
        // We keep track of the timetoken of the first message of the array
        // so it will be easier to fetch the previous messages later
        this.firstMessageTimeToken = null;
        this.messagesAllFetched = false;
        // The service will return useful functions to interact with.
        return {};
    }
]);

→ Add an init function that will run when the factory is instantiated. It should subscribe to the messages channel in order to receive, send, fetch previous messages and every operation you can do through the PubNub network.

The codes should looks like this :

var init = function() {
    Pubnub.subscribe({
        channel: self.channel,
        triggerEvents: ['callback']
    });
}
init();

Subscribing to New Messages

Subscribing to New Messages

A dedicated function called when there is a new message

→ Let’s implement our first method that we are going to be able to call providing a callback function. This callback function will be performed each time a new message is received from the PubNub network:

MessageService.subscribeNewMessage(function(m){
 console.log(‘There is a new message and I am aware of that !)
)})

The PubNub AngularJS SDK is already emitting events on the rootScope, when it receive a new message, and you can simply use it to forward the event to our subscribeNewMessage function.

Here is how our subcribeNewMessage function looks like:

var subcribeNewMessage = function(callback) {
   $rootScope.$on(Pubnub.getMessageEventNameFor(self.channel), callback);
};

Automatically store the new messages received

→ Take the opportunity to use the previous method in the init function to automatically store the new messages received in the messages array.

var init = function() {
  //...  
  subcribeNewMessage(function(ngEvent, m) {
          self.messages.push(m)
          $rootScope.$digest()
      }
});

Don’t forget to call $rootScope.$digest() so that the entire app can be aware that the Message factory has changed.

Populate the Message Factory

Populate the Message Factory

Now that the new messages received are automatically stored, it will be great to populate the factory with the previous messages. With PubNub, it’s quite easy to achieve this using the history endpoint. Here is an example to fetch 20 messages.

PubNub history feature:

Pubnub.history({
   channel: ‘A-CHANNEL-NAME’,
   callback: function(payload){ console.log(‘I’m called when the history is fetched’)},
   count: 20, // number of messages to retrieve, 100 is the default
   reverse: false, // order to retrieve the messages, false is the default
   // You can define a timeframe for fetching the messages with START and END
   start: 13827485876355504 , // [OPTIONAL] starting timestamp to start retrieving the messages from 
   end: 13827475876355504, // [OPTIONAL] ending timestamp to finish retrieving the messages from 
})};

→ Create a populate function that calls the history method.

→ Ensure to store the messages retrieved, update the variables timeTokenFirstMessage and messagesAllFetched and emit a factory:message:populated event.

var populate = function() {
    
    var defaultMessagesNumber = 20;
    
    Pubnub.history({
        channel: self.channel,
        callback: function(m) {
            // Update the timetoken of the first message
            angular.extend(self.messages, m[0]);

            if (m[0].length < defaultMessagesNumber) {
                self.messagesAllFetched = true;
            }

            self.timeTokenFirstMessage = m[1]
            $rootScope.$digest()
            $rootScope.$emit('factory:message:populated')
        },
        count: 20
    });
};

Using angular.extend function

You have probably noticed I used the angular.extend function to update the array with the new messages function. It’s really important to do so and not overriding or replacing the message array with a new one in order to keep the same array reference

Why it matter ? When you display the content of an array through an ng-repeat, AngularJS automatically set up a collection watcher on this array reference. If you change the reference on the fly, the watcher won’t be able to keep track of the changes and the view won’t be updated.

Getting the Messages

This method is easy and short to implement but we needed to implement the populate() method to populate the method if the messages collection is empty

Here is the code:

var getMessages = function() {
    if (_.isEmpty(self.messages))
        populate();
    return self.messages;
};

Sending the Messages

Sending the Messages

→ Create a function that will send a message to the PubNub network. → Append a uuid for each message sent so we can keep track of each message sent.

Here is the code of the sendMessage function:

var sendMessage = function(messageContent) {
    // Don't send an empty message 
    if (_.isEmpty(messageContent))
        return;

    Pubnub.publish({
        channel: self.channel,
        message: {
            uuid: (Date.now() + currentUser),
            content: messageContent,
            sender_uuid: currentUser,
            date: Date.now()
        }
    });
};

Fetching the Previous Messages on the Fly

Fetching the Previous Messages on the Fly

In the last step for our chat app, we want to implement an infinite scroll feature that will load the previous messages. When scrolling and reaching the top of the screen, we need to trigger a method in order to fetch the previous messages and add them to the top of the collection.

→ Let’s implement a fetchPreviousMessages method and make the method return a promise so we can be notified when the former messages have been loaded.

→ Once again, we will use the History API but we will add the start flag to set the starting timestamp to fetch the previous messages from. Use the self.timeTokenFirstMessage variable of the factory it’s designed for.

Here is the code :

var fetchPreviousMessages = function() {

    var defaultMessagesNumber = 10;
    var deferred = $q.defer()

    Pubnub.history({
        channel: self.channel,
        callback: function(m) {
            // Update the timetoken of the first message
            self.timeTokenFirstMessage = m[1]
            Array.prototype.unshift.apply(self.messages, m[0])

            if (m[0].length < defaultMessagesNumber) {
                self.messagesAllFetched = true;
            }

            $rootScope.$digest()
            deferred.resolve(m)

        },
        error: function(m) {
            deferred.reject(m)
        },
        count: 10,
        start: self.timeTokenFirstMessage,
        reverse: false
    });

    return deferred.promise
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment