Create a live comments feature with Ionic

Introduction

Introduction

Sentiment analysis is a way to evaluate written or spoken language to determine if the expression is favorable, unfavorable, or neutral, and to what degree. You can read up about it here.

Live comments offer a realtime comment experience that doesn’t require a page refresh. You see comments when they’re posted.

Using Ionic, you can build a mobile app with HTML, CSS/SCSS, and JavaScript. With Pusher Channels we can enable realtime messaging in the chat using the Pushers pub/sub pattern.

We’ll be building a live comments application using Pusher Channels, Ionic and the sentiment library for rating suggestions based on the context of messages received.

Using our application, users can see the rating of each post using sentiment analysis.

Here’s a demo of the final product:

ionic-comments-sentiment-demo

Prerequisites

To follow this tutorial a basic understanding of Angular, Ionic and Node.js is required. Please ensure that you have Node and npm installed before you begin.

If you have no prior knowledge of Ionic, kindly follow the tutorial here. Come back and finish the tutorial when you’re done.

We’ll be using these tools to build out our application:

Setup and folder structure

To get started, we will use the CLI (command line interface) provided by the Ionic team to initialize our project.

First, install the CLI by running npm install -g ionic cordova. NPM is a package manager used for installing packages. It will be available on your PC if you have Node installed.

To create a new Ionic App using the CLI, open a terminal and run:

    ionic start liveComments blank

The command is merely telling the CLI to create a new app called liveComments and it should make use of the blank starter template. While the setup is running, you might get a prompt "Would you like to integrate your new app with Cordova to target native iOS and Android?". If you want to start running or testing the application on a mobile device or emulator as you build, then choose yes by typing Y and pressing Enter on your keyboard, else type N and continue. You might get another prompt "If you would like to integrate ionic pro ?" , we wouldn’t need that in this tutorial so just type N and if you would like to extend the project into production with your team choose Y.

Open the newly created liveComments. Your folder structure should look something like this:

1liveComments /
2      node_modules /
3      src / 
4        app /
5          assets /
6          pages /
7            home /
8              home.html
9              home.ts
10              home.scss

Open a terminal inside the app folder and start the application by running:

    ionic serve 

Automatically your default browser should open, and you should see the screenshot below if everything went well.

ionic-chat-sentiment-ionic-serve

Building our server

Now that we have our Ionic application running let’s build our server. To do this, we’ll need to install Express. Express is a fast, unopinionated, minimalist web framework for Node.js. We’ll use this to receive requests from our Ionic application.

Run npm install express on a terminal inside the root folder of your project to install Express. Create a file called server.js at the root of the project and update it with the code snippet below:

1// server.js   
2        require('dotenv').config();
3        const express = require('express');
4        const bodyParser = require('body-parser');
5    
6        const app = express();
7        const port = process.env.PORT || 4000;
8    
9        app.use(bodyParser.json());
10        app.use(bodyParser.urlencoded({ extended: false }));
11        app.use((req, res, next) => {
12          res.header('Access-Control-Allow-Origin', '*');
13          res.header(
14            'Access-Control-Allow-Headers',
15            'Origin, X-Requested-With, Content-Type, Accept'
16          );
17          next();
18        });
19    
20        app.listen(port, () => {
21          console.log(`Server started on port ${port}`);
22        });

We referenced three packages that haven’t been installed, body-parser, pusher and dotenv. Install these packages by running the following command in your terminal.

        npm i body-parser pusher dotenv
  • body-parser is a package used to parse incoming request bodies in a middleware before your handlers, available under the req.body property.
  • dotenv is a zero-dependency module that loads environment variables from a .env file into process.env. This package is used to avoid adding sensitive information like the appId and secret into our codebase directly.
  • The dotenv package will load the variables provided in our .env file into our environment.
  • CORS: The calls to our endpoint will be coming in from a different origin. Therefore we need to make sure we include the CORS headers (Access-Control-Allow-Origin). If you are unfamiliar with the concept of CORS headers, you can find more information here.
  • The dotenv library should always be initialized at the start of our file because we need to load the variables as early as possible to make them available throughout the application.

We also installed the Pusher library as a dependency. Create a free sandbox Pusher account or sign in.

Let’s create a .env file to load the variables we’ll be needing into the Node environment. Create the file in the root folder of your project and update it with the code below.

Your .env file should look something like the snippet below. We’ll add our Pusher appId, key and secret provided here.

1PUSHER_APP_ID=<APP_ID>
2    PUSHER_KEY=<PUSHER_KEY>
3    PUSHER_SECRET=<PUSHER_SECRET>
4    PUSHER_CLUSTER=<PUSHER_CLUSTER>

If you noticed, we added the dotenv package at the start of our file. This is done because we need to make the variables available throughout the file.

Send comments

To enable users to send and receive messages, we’ll create a route to handle incoming requests. Update your server.js file with the code below:

1// server.js
2    require('dotenv').config();
3    const express = require('express');
4    const bodyParser = require('body-parser');
5    const Pusher = require('pusher');
6    const pusher = new Pusher({
7        appId: process.env.PUSHER_APP_ID,
8        key: process.env.PUSHER_KEY,
9        secret: process.env.PUSHER_SECRET,
10        cluster: process.env.PUSHER_CLUSTER,
11        encrypted: true,
12    });
13    
14    const app = express();
15    const port = process.env.PORT || 4000;
16    app.use(bodyParser.json());
17    app.use(bodyParser.urlencoded({ extended: false }));
18    app.use((req, res, next) => {
19    res.header('Access-Control-Allow-Origin', '*');
20    res.header(
21        'Access-Control-Allow-Headers',
22        'Origin, X-Requested-With, Content-Type, Accept'
23    );
24      next();
25    });
26    app.post('/message', async (req, res) => {
27      const { body } = req
28      const { comment } = body
29      const data = {
30        comment,
31        timeStamp: new Date(),
32      };
33      try {
34        pusher.trigger('comments', 'message', data);
35      } catch (e) {}
36      res.json(data);
37    })
38    app.listen(port, () => {
39      console.log(`Server started on port ${port}`);
40    });
  • We created a POST /message route which, when hit, triggers a Pusher event.
  • We used object destructuring to get the body of the request, we also got the comment in the request body sent by the user.
  • The data object contains the text and name sent by the user. It also includes a timestamp.
  • The trigger method which takes a trigger identifier, triggers our chat channel.
  • The trigger function also takes a second argument, the event name (message), and a payload(data).
  • We still go ahead to respond with an object containing the data variable we created.

Sentiment analysis

Sentiment analysis uses data mining processes and techniques to extract and capture data for analysis in order to discern the subjective opinion of a document or collection of documents, like blog posts, reviews, news articles, and social media feeds like tweets and status updates. - Technopedia.

Using sentiment analysis, we’ll analyze the messages sent to determine the attitude of the sender. With the data gotten from the analysis, we’ll determine the emojis to suggest to the user.

We’ll use the Sentiment JavaScript library for analysis. To install this library, open a terminal in the root folder of your project and run the following command.

        npm install sentiment

We’ll update our POST /messages route to include analysis of the messages being sent in. Update your server.js with the code below.

1// server.js
2    
3    require('dotenv').config();
4    const express = require('express');
5    const bodyParser = require('body-parser');
6    const Pusher = require('pusher');
7    const pusher = new Pusher({
8        appId: process.env.PUSHER_APP_ID,
9        key: process.env.PUSHER_KEY,
10        secret: process.env.PUSHER_SECRET,
11        cluster: process.env.PUSHER_CLUSTER,
12        encrypted: true,
13    });
14    const Sentiment = require('sentiment');
15    const sentiment = new Sentiment();
16    const app = express();
17    const port = process.env.PORT || 4000;
18    app.use(bodyParser.json());
19    app.use(bodyParser.urlencoded({ extended: false }));
20    app.use((req, res, next) => {
21    res.header('Access-Control-Allow-Origin', '*');
22    res.header(
23        'Access-Control-Allow-Headers',
24        'Origin, X-Requested-With, Content-Type, Accept'
25      );
26      next();
27    });
28    app.post('/message', async (req, res) => {
29      const { body } = req
30      const { message } = body
31      const result = sentiment.analyze(message);
32      const comparative = result.comparative;
33      const data = {
34        message,
35        score : result.score,
36        timeStamp: new Date(),
37      };
38      try {
39        pusher.trigger('comments', 'message', data);
40      } catch (e) {}
41      res.json(data);
42    })
43    app.listen(port, () => {
44      console.log(`Server started on port ${port}`);
45    });
  • Include the sentiment library in the project.
  • result: here, we analyze the message sent in by the user to determine the context of the message.
  • comparative: this is the comparative score gotten after analyzing the message.
  • A new property (score) is added to the response data containing the message’s score after analysis.

You can now start the server by running node server.js in a terminal in the root folder of the project.

Building the UI

Let’s begin building the interface, open home.html and update with the code below :

1// src/pages/home/home.html
2    
3    <ion-header>
4      <ion-navbar>
5        <ion-title>
6          Live Comments
7        </ion-title>
8      </ion-navbar>
9    </ion-header>
10    <ion-content padding>
11      <!-- example blog post-->
12      <h1 text-center> Interesting Article</h1>
13      <p>
14        Once you're done creating the quality content, you still have the challenge of presenting it that clearly dictates what your blog is about. Images, text, and links need to be shown off just right -- otherwise, readers might abandon your content if it's not aesthetically showcased in a way that's both appealing and easy to follow. </p>
15        <div class="flex-items">
16            <ion-row>
17                <ion-col col-6>
18                      <h1 float-right>{{rating.good}} <ion-icon  name="arrow-round-up" style="color : #90EE90; font-size: 25px;"></ion-icon></h1>
19                </ion-col>
20                <ion-col col-6>
21                    <h1>{{rating.bad}} <ion-icon name="arrow-round-down" style="color: #FF0000; font-size: 25px;"></ion-icon></h1>
22                </ion-col>
23              </ion-row>
24        </div>
25        <div class="comment-box">
26          <ion-card *ngFor="let comment of comments">
27            <ion-card-content>
28                  <strong>{{comment.message}}</strong>
29                  <p>
30                   {{comment.timeStamp | date : 'H:mm a'}}
31                  </p>
32            </ion-card-content>
33           
34          </ion-card>
35        </div>
36      <ion-footer padding>
37        <ion-textarea [(ngModel)]="message" type="text" placeholder="Comment .... "></ion-textarea>
38        <button ion-button small float-right round (click)="sendComment()">Send</button>
39      </ion-footer>
40      
41    </ion-content>

In the code snippet above:

  • We have an ion-input element for our users’ comments.
  • A send button to send our comment to the server.
  • We also used the *ngFor directive to loop through all our comments from the server and render them inside an ion-card element.

Open Home.ts file and update it like so:

1// src/pages/home/home.ts
2    
3    import { HttpClient } from '@angular/common/http';
4    import { Component } from '@angular/core';
5    import { NavController } from 'ionic-angular';
6    
7    
8    @Component({
9      selector: 'page-home',
10      templateUrl: 'home.html'
11    })
12    export class HomePage {
13      comments = [];
14      message: string;
15      url: string = 'http://localhost:4000/message'
16      rating = {
17        bad : 0,
18        good : 0,
19      }
20      constructor(public navCtrl: NavController, public http : HttpClient) {}
21    
22      sendComment(){
23        if(this.message != ''){
24          this.http.post(this.url, {message : this.message}).subscribe((res : any) => {
25            this.message = '';
26          })
27        }
28      }
29    
30      ionViewDidLoad(){}
31    }

sendComment() : this method uses the native HttpClient to make requests to the server. The POST method takes a URL and the request body as parameters. We then append the data returned to the array of comments.

To make use of the HttpClient service, we’ll need to import the HttpClientModule into the app.module.ts file. Also to make use of form-related directives, we’ll need to import the FormsModule. Update your app module file as follows:

1// src/app/app.module.ts
2    
3    ...
4    import { HttpClientModule } from '@angular/common/http';
5    import { FormsModule } from '@angular/forms';
6    ...
7    imports: [
8    ...
9    FormsModule,
10    HttpClientModule
11    ]
12    ...

Styling

Open home.scss and update with the code below:

1page-home {
2        ion-textarea {
3            border-bottom: 1px solid #000000;
4            margin: 0px 5px;
5        }
6        p {
7            text-align: justify;
8        }
9        .comment-box {
10            overflow: scroll;
11            height: 220px;
12        }
13    }

Introducing Pusher

So far we have an application that allows users to send in comments, but we want to update the comments under the post in realtime. We’ll include the Pusher library in our application to enable realtime features like seeing comments as they come in without having to refresh the page.

Open the index.html file and paste the Pusher CDN like so:

1...
2    
3      <script src="https://js.pusher.com/4.1/pusher.min.js"></script>
4      <!-- add to homescreen for ios --> 
5    ...

Now that Pusher has been made available in our project, we’ll create a Pusher Provider to be used application wide. The Ionic CLI can aid in the service creation. Open a terminal in your project’s root folder and run the following command.

    ionic generate provider pusher-service

This command simply generates a provider named pusher-service. Now open pusher-service.ts and update with the code below :

1// src/provider/pusher-service/pusher-service.ts
2    
3    import { HttpClient } from '@angular/common/http';
4    import { Injectable } from '@angular/core';
5    declare const Pusher: any;
6    @Injectable()
7    export class PusherServiceProvider {
8      channel;
9      constructor(public http: HttpClient) {
10      var pusher = new Pusher("PUSHER_KEY", { 
11      cluster: 'eu',
12      encrypted: true,
13      });
14      this.channel = pusher.subscribe('comments');
15    }
16    
17      public init(){
18       return this.channel;
19      }
20    }

First, we initialize Pusher in the constructor. The init() method returns the Pusher property we created. Ensure you replace the PUSHER_KEY string with your actual Pusher key.

To make the provider available in the application, import it into the app.module.ts file.

1// ../src/app/app.module.ts
2    
3    import { PusherServiceProvider } from '../providers/pusher-service/pusher-service';
4    ....
5    providers: [
6    StatusBar,
7    SplashScreen,
8    {provide: ErrorHandler, useClass: IonicErrorHandler},
9    PusherServiceProvider
10    ]
11    ....

We’ll make use of this provider in our home page, by binding to the message event and add the comment returned in the event and add it to the comments array. This will be done in the ionViewDidLoad lifecycle.

1// ../src/pages/home/home.ts
2    
3    import { PusherServiceProvider } from '../../providers/pusher-service/pusher-service';
4    
5    constructor(public navCtrl: NavController, public http : HttpClient, private pusher : PusherServiceProvider) {}
6    
7    ...
8    
9    ionViewDidLoad(){
10     const channel = this.pusher.init();
11        channel.bind('message', (data) => {
12          if(data.score >= 1){
13            this.rating.good = this.rating.good + 1;
14          }
15          else{
16            this.rating.bad = this.rating.bad + 1;
17          }
18          this.comments.push(data);
19        });
20    }

At this point, your application should have realtime updates when comments are placed. Ensure that the server is running alongside the Ionic development server. If not, run node server and ionic serve in two separate terminals. Both terminals should be opened in the root folder of your project.

To test the realtime functionality of the application, open two browsers side-by-side and comment. You’ll notice that votes placed on one reflect on the other browser.

Conclusion

Using the sentiment analysis, we are able to see the ratio of good comments to bad. Using Pusher Channels, we were able to implement live comments functionality in our application. You can view the source code for the demo here.