Build

Device Presence and Custom State with PubNub Angular 2

6 min read Michael Carroll on Apr 13, 2017

We’re back for Part Two of our series on the new PubNub Angular 2 SDK. In Part One, we gave an overview of what’s new in the PubNub JavaScript and Angular 2 SDK, and used our core Publish/Subscribe feature to build a basic real-time chat app, in this case, sending and receiving data, and saving that message history with Storage & Playback. We recommend starting there, and coming back to this post when you’ve gone through that tutorial.

In this tutorial, we walkthrough more advanced features of the PubNub Angular 2 API, including Presence (online/offline detection) and Custom State (user or device attributes such as geolocation).

Obtaining your PubNub Developer Keys

You’ll first need your PubNub publish/subscribe keys. If you haven’t already, sign up for PubNub. Once you’ve done so, go to the PubNub Admin Portal to get your unique keys.

The publish and subscribe keys look like UUIDs and start with “pub-c-” and “sub-c-” prefixes respectively. Keep those handy – you’ll need to plug them in when initializing the PubNub object in your HTML5 app below.

Note: for this article, you’ll want to make sure to enable the Storage and Playback and Presence features in your PubNub Application configuration to take advantage of the history() and here_now() features.

Diving into the Code – the User Interface

You’ll want to grab these 126 lines of HTML & JavaScript and save them to a file called pubnub_angular2_extended.html.

The first thing you should do after saving the code is to replace two values in the JavaScript:

  • YOUR_PUB_KEY: with the PubNub publish key mentioned above.
  • YOUR_SUB_KEY: with the PubNub subscribe key mentioned above.

If you don’t, the UI will not be able to communicate with anything and probably clutter your console log with entirely too many errors.

For your convenience, this code is also available as a Gist on GitHub, and a Codepen as well. Enjoy!

Dependencies

First up, we have the JavaScript code & CSS dependencies of our application.

<!DOCTYPE html>
<html>
  <head>
    <title>Angular 2</title>
    <script src="https://unpkg.com/core-js@2.4.1/client/shim.min.js"></script>
    <script src="https://unpkg.com/zone.js@0.7.2/dist/zone.js"></script>
    <script src="https://unpkg.com/reflect-metadata@0.1.9/Reflect.js"></script>
    <script src="https://unpkg.com/rxjs@5.0.1/bundles/Rx.js"></script>
    <script src="https://unpkg.com/@angular/core/bundles/core.umd.js"></script>
    <script src="https://unpkg.com/@angular/common/bundles/common.umd.js"></script>
    <script src="https://unpkg.com/@angular/compiler/bundles/compiler.umd.js"></script>
    <script src="https://unpkg.com/@angular/platform-browser/bundles/platform-browser.umd.js"></script>
    <script src="https://unpkg.com/@angular/forms/bundles/forms.umd.js"></script>
    <script src="https://unpkg.com/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js"></script>
    <script src="https://unpkg.com/pubnub@4.3.3/dist/web/pubnub.js"></script>
    <script src="https://unpkg.com/pubnub-angular2@1.0.0-beta.8/dist/pubnub-angular2.js"></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" />
  </head>

For folks who have done front-end implementation with Angular2 before, these should be the usual suspects:

  • CoreJS ES6 Shim, Zone.JS, Metadata Reflection, and RxJS : Dependencies of Angular2.
  • Angular2 : core, common, compiler, platform-browser, forms, and dynamic platform browser modules.
  • PubNub JavaScript client: to connect to our data stream integration channel.
  • PubNub Angular2 JavaScript client: provides PubNub services in Angular2 quite nicely indeed.

In addition, we bring in the CSS features:

  • Bootstrap: in this app, we use it just for vanilla UI presentation.

Overall, we were pretty pleased that we could build a nifty UI with so few dependencies. And with that… on to the UI!

The User Interface

Here’s what we intend the UI to look like:

The User Interface

 

The UI is pretty straightforward – everything is inside a main-component tag that is managed by a single component that we’ll set up in the Angular2 code.

<body>
  <main-component>
    Loading...
  </main-component>

Let’s skip forward and show that Angular2 component template. The h3 heading should be pretty self-explanatory, along with the tag that displays the userId. To demonstrate custom user state (a “mood” attribute), we provide a select box for selecting the user’s mood.

Note that in Angular2, we use 2-way binding for the mood variable, but since we want to do 3-way binding with PubNub, we register a (ngModelChange) handler.

<div class="container">
  <h3>Welcome to Channel: {{ channelName }}</h3>
  You are: <b>{{userId}}</b>; Your mood is :
  <select [(ngModel)]="mood" (ngModelChange)="updateMood($event)" >
    <option value="happy">happy</option>
    <option value="neutral">neutral</option>
    <option value="sad">sad</option>
  </select>
  <br />

We present a basic list of online users with the corresponding mood using an ngFor tag.

  <div class="well">
    <p>Users Online ({{ usersConnected }})</p>
    <ul>
      <li *ngFor="let o of occupants">{{ o.uuid }} ({{o.state?.mood}})</li>
    </ul>
  </div>

We also provide a simple text input for a chat message, as well as a button to perform the publish() action to send the message out on the PubNub channel.

  <hr/>
  <p>Enter a new message:</p>
  <input type="text" [(ngModel)]="newMessage" />
  <button class="btn btn-primary" (click)="publish()">Send!</button>
  <br/>
  <br/>
  <ul class="list-unstyled">
    <li *ngFor="let item of messages">{{ item.message }}</li>
  </ul>
</div>

The last part of the UI consists of a simple list of messages; We iterate over the messages in the component scope using a trusty ngFor. Each message includes the chat text (in your application, you may choose to use a structured JSON message instead).

And that’s it – a functioning real-time UI in just a handful of code (thanks, Angular2)!

The Angular2 Code

Right on! Now we’re ready to dive into the Angular2 code. It’s not a ton of JavaScript, so this should hopefully be pretty straightforward.

The first lines we encounter set up our application (with a necessary dependency on the PubNub AngularJS service) and a single component (which we dub main-component).

<script>
var app = window.app = {};
app.main_component = ng.core.Component({
    selector: 'main-component',
    template: `...see previous...` 

The component has a constructor that takes care of initializing the PubNub service and configuring the channel name, (random) user ID, default mood, presence and message-related variables.

    }).Class({
        constructor: [PubNubAngular, function(pubnubService){
            var self = this;
            self.pubnubService = pubnubService;
            self.channelName = 'angular2-chat';
            self.userId   = 'User_' + Math.round(Math.random() * 1000);
            self.mood = 'happy';
            self.messages = [];          
            self.newMessage = '';
            self.usersConnected = 0;
            self.occupants = [];

Early on, we initialize the pubnubService with our credentials.

            pubnubService.init({
                publishKey: 'YOUR_PUB_KEY',
                subscribeKey: 'YOUR_SUB_KEY',
                uuid: this.userId
            });

We subscribe to the relevant channel and configure an event handler that will unshift() any incoming messages into the self.messages array.

            pubnubService.subscribe({channels: [self.channelName], triggerEvents: true, withPresence: true});
            pubnubService.getMessage(this.channelName, function(msg) {
                self.messages.unshift(msg);
            });

To implement the persistence function, we call history() which returns a Promise. To handle the Promise, we call the then() function with a callback that reads the messages collection from the incoming history event into the self.messages collection. Note that we use x.entry because that’s the new attribute for message data in the PubNub history API.

            pubnubService.history({channel: self.channelName, count:50}).then(function(msg) {
                msg.messages.forEach(function(x) { self.messages.unshift({message:x.entry}); });
            });

To implement the user list, we call getPresence() with a nested handler to call the hereNow() function. We make sure to set the includeUUIDs and includeState parameters so that we can retrieve the user IDs and mood attributes.

            pubnubService.getPresence(self.channelName, (presence) => {
                self.usersConnected = presence.occupancy;
                self.pubnubService.hereNow({
                    channels: [self.channelName],
                    includeUUIDs: true,
                    includeState: true
                }).then(function (response) {
                    self.occupants = response.channels[self.channelName].occupants;
                }).catch((error) => {});
            });

The last thing in the constructor is to call the updateMood() function to set the default user mood in PubNub.

          self.updateMood();
      }],

We create a publish() event handler that performs the action of publishing the new message to the PubNub channel.

          publish: function(){
              if (this.newMessage !== '') {
                  this.pubnubService.publish({channel: this.channelName, message: '[' + this.userId + '] ' + this.newMessage});
                  this.newMessage = '';
              }
          },

We also create an updateMood() event handler that performs the action of sending the new user state to PubNub.

          updateMood: function(){
              this.pubnubService.setState({state:{mood:this.mood}, uuid:this.userId, channels:[this.channelName]});
          }
      });

Now that we have our new component, we can create a main module for the Angular2 app that uses it. This is pretty standard boilerplate that configures dependencies on the Browser and Forms modules and the PubNubAngular service.

app.main_module = ng.core.NgModule({
    imports: [ng.platformBrowser.BrowserModule, ng.forms.FormsModule],
    declarations: [app.main_component],
    providers: [PubNubAngular],
    bootstrap: [app.main_component]
}).Class({
    constructor: function(){}
});

Finally, we bind the application bootstrap initialization to the browser DOM content loaded event.

document.addEventListener('DOMContentLoaded', function(){
    ng.platformBrowserDynamic.platformBrowserDynamic().bootstrapModule(app.main_module);
});

We mustn’t forget close out the HTML tags accordingly.

});
</script>
</body>
</html>

Not too shabby for about 126 lines of HTML & JavaScript!

Conclusion

Thank you so much for joining us in our extended Angular2 application with chat, history and presence! Hopefully it’s been a useful experience learning about what’s new in the JavaScript and Angular2 worlds.

Stay tuned, and please reach out anytime if you feel especially inspired or need any help!

0