Create a live comments feature with Ionic
You will need Node and npm installed on your machine. A basic understanding of Ionic development will be helpful.
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 we can enable realtime messaging in the chat using Pusher’s pub/sub pattern.
We’ll be building a live comments application using Pusher, 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:
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:
liveComments /
node_modules /
src /
app /
assets /
pages /
home /
home.html
home.ts
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.
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:
// server.js
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = process.env.PORT || 4000;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
next();
});
app.listen(port, () => {
console.log(`Server started on port ${port}`);
});
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 theappId
andsecret
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. Follow this link to create a Pusher account if you haven’t done so already.
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.
PUSHER_APP_ID=<APP_ID>
PUSHER_KEY=<PUSHER_KEY>
PUSHER_SECRET=<PUSHER_SECRET>
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:
// server.js
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const Pusher = require('pusher');
const pusher = new Pusher({
appId: process.env.PUSHER_APP_ID,
key: process.env.PUSHER_KEY,
secret: process.env.PUSHER_SECRET,
cluster: process.env.PUSHER_CLUSTER,
encrypted: true,
});
const app = express();
const port = process.env.PORT || 4000;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
next();
});
app.post('/message', async (req, res) => {
const { body } = req
const { comment } = body
const data = {
comment,
timeStamp: new Date(),
};
try {
pusher.trigger('comments', 'message', data);
} catch (e) {}
res.json(data);
})
app.listen(port, () => {
console.log(`Server started on port ${port}`);
});
- 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 thetext
andname
sent by the user. It also includes a timestamp. - The
trigger
method which takes a trigger identifier, triggers ourchat
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.
// server.js
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const Pusher = require('pusher');
const pusher = new Pusher({
appId: process.env.PUSHER_APP_ID,
key: process.env.PUSHER_KEY,
secret: process.env.PUSHER_SECRET,
cluster: process.env.PUSHER_CLUSTER,
encrypted: true,
});
const Sentiment = require('sentiment');
const sentiment = new Sentiment();
const app = express();
const port = process.env.PORT || 4000;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
next();
});
app.post('/message', async (req, res) => {
const { body } = req
const { message } = body
const result = sentiment.analyze(message);
const comparative = result.comparative;
const data = {
message,
score : result.score,
timeStamp: new Date(),
};
try {
pusher.trigger('comments', 'message', data);
} catch (e) {}
res.json(data);
})
app.listen(port, () => {
console.log(`Server started on port ${port}`);
});
- 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 :
// src/pages/home/home.html
<ion-header>
<ion-navbar>
<ion-title>
Live Comments
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<!-- example blog post-->
<h1 text-center> Interesting Article</h1>
<p>
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>
<div class="flex-items">
<ion-row>
<ion-col col-6>
<h1 float-right>{{rating.good}} <ion-icon name="arrow-round-up" style="color : #90EE90; font-size: 25px;"></ion-icon></h1>
</ion-col>
<ion-col col-6>
<h1>{{rating.bad}} <ion-icon name="arrow-round-down" style="color: #FF0000; font-size: 25px;"></ion-icon></h1>
</ion-col>
</ion-row>
</div>
<div class="comment-box">
<ion-card *ngFor="let comment of comments">
<ion-card-content>
<strong>{{comment.message}}</strong>
<p>
{{comment.timeStamp | date : 'H:mm a'}}
</p>
</ion-card-content>
</ion-card>
</div>
<ion-footer padding>
<ion-textarea [(ngModel)]="message" type="text" placeholder="Comment .... "></ion-textarea>
<button ion-button small float-right round (click)="sendComment()">Send</button>
</ion-footer>
</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 anion-card
element.
Open Home.ts
file and update it like so:
// src/pages/home/home.ts
import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
comments = [];
message: string;
url: string = 'http://localhost:4000/message'
rating = {
bad : 0,
good : 0,
}
constructor(public navCtrl: NavController, public http : HttpClient) {}
sendComment(){
if(this.message != ''){
this.http.post(this.url, {message : this.message}).subscribe((res : any) => {
this.message = '';
})
}
}
ionViewDidLoad(){}
}
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:
// src/app/app.module.ts
...
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
...
imports: [
...
FormsModule,
HttpClientModule
]
...
Styling
Open home.scss
and update with the code below:
page-home {
ion-textarea {
border-bottom: 1px solid #000000;
margin: 0px 5px;
}
p {
text-align: justify;
}
.comment-box {
overflow: scroll;
height: 220px;
}
}
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:
...
<script src="https://js.pusher.com/4.1/pusher.min.js"></script>
<!-- add to homescreen for ios -->
...
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 :
// src/provider/pusher-service/pusher-service.ts
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
declare const Pusher: any;
@Injectable()
export class PusherServiceProvider {
channel;
constructor(public http: HttpClient) {
var pusher = new Pusher("PUSHER_KEY", {
cluster: 'eu',
encrypted: true,
});
this.channel = pusher.subscribe('comments');
}
public init(){
return this.channel;
}
}
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.
// ../src/app/app.module.ts
import { PusherServiceProvider } from '../providers/pusher-service/pusher-service';
....
providers: [
StatusBar,
SplashScreen,
{provide: ErrorHandler, useClass: IonicErrorHandler},
PusherServiceProvider
]
....
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.
// ../src/pages/home/home.ts
import { PusherServiceProvider } from '../../providers/pusher-service/pusher-service';
constructor(public navCtrl: NavController, public http : HttpClient, private pusher : PusherServiceProvider) {}
...
ionViewDidLoad(){
const channel = this.pusher.init();
channel.bind('message', (data) => {
if(data.score >= 1){
this.rating.good = this.rating.good + 1;
}
else{
this.rating.bad = this.rating.bad + 1;
}
this.comments.push(data);
});
}
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.
2 July 2018
by Christian Nwamba