chatengine-angular2-chat-tutorial

Let’s build a fully-functioning chat app for web and mobile web using Angular2 and ChatEngine. If you’re unfamiliar with ChatEngine, it offers the infrastructure and a ton of APIs for building chat apps. That includes a number of plugins, a bunch that we’ll implement today to build out the core features of any chat app.

The full GitHub repo for this project is available here.

Step 1: Setup

Before getting started, you’ll have to setup ChatEngine via our ChatEngine Quick Start. This enables you to use your own PubNub application keyset and automagically sets up your account to use ChatEngine. It’ll take a couple minutes at most, then you can come back here and continue on.

With that done, let’s set up the environment.

npm install -g @angular/cli

ng new chatengine-simple-demo

cd chatengine-simple-demo

npm install chat-engine --save

npm install chat-engine-random-username --save

npm install chat-engine-online-user-search --save

npm install chat-engine-typing-indicator --save

To start we will add bootstrap to the index.html:

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

Step 2: Bind ChatEngine to Angular

In order to use the benefices of dependency injection offered by Angular, we will create a service into the folder app called chatengine.ts. This wraps ChatEngine and is injected as a provider. It also lets us use ChatEngine’s instance through Angular2 and separate the functionality of our App in different components which each one of these will have a particular responsibility.

This service offers the ChatEngine instance which will be created as soon as Angular2 is installed in the list of providers and the method is exposed.

import { Injectable } from '@angular/core';
import { ChatEngineCore } from 'chat-engine';

@Injectable()
export class ChatEngine {
  instance: any;
  create: any;
  plugin: any;
  constructor() {
    this.instance = ChatEngineCore.create(
      {
        publishKey: 'YOUR PUBLISH KEY HERE',
        subscribeKey: 'YOUR SUBSCRIBE KEY HERE'
      },
      {
        debug: true,
        globalChannel: 'chat-engine-angular2-simple'
      });

    this.create = ChatEngineCore.create.bind(this);
    this.plugin = ChatEngineCore.plugin;
  }
}

Having set up our service, it’s time to inject it into the list of providers of our Angular app. The file app.module.ts looks like this:

import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { NgModule } from '@angular/core';
import { ChatEngine } from './chatEngine';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [ChatEngine],
  bootstrap: [AppComponent]
})
export class AppModule { }

With that, we need to introduce our UI.  app.component.html is the template of the main component.

<div class="container-fluid">
  <div class="row">
    <div class="col-md-6">
    </div>
    <div class="col-md-6">
    </div>
  </div>
</div>

Inside of app.component.ts, we inject the service:

import { Component, OnInit } from '@angular/core';
import { ChatEngine } from './chatEngine';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  private ce: any;

  constructor(private chatEngine: ChatEngine) {
    this.ce = chatEngine;
  }

  ngOnInit() {
  }
}

Step 3: Connect ChatEngine

In this step, we will connect ChatEngine. We have to add the attribute inside of our service in order to get access to our session:

import { Injectable } from '@angular/core';
import { ChatEngineCore } from 'chat-engine';

@Injectable()
export class ChatEngine {
  instance: any;
  create: any;
  plugin: any;
  me: any = { state: {} };
  constructor() {
    ...
  }
}

From our main component, the connection into the hook ngOnInit is invoked in the first render of the component. It will set the field me which represent our user:

import { Component, OnInit } from '@angular/core';
import { ChatEngine } from './chatEngine';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  private ce: any;

  constructor(private chatEngine: ChatEngine) {
    this.ce = chatEngine;
  }

  ngOnInit() {
    this.ce.instance.connect(new Date().getTime(), {}, 'auth-key');
    this.ce.instance.on('$.ready', (data) => {
      this.ce.me = data.me;
    });
  }
}

We will add some HTML elements to the template of our main component app.component.html in order to display the current state of our connection.

<div class="container-fluid">
  <div class="row">
    <div class="col-md-6">
      <div class="card">
        <div class="card-block">
          <h4 class="card-title">ChatEngine</h4>
          <p class="card-text">Your are connected with uuid {{ce.me.uuid}}</p>
        </div>
      </div>
    </div>
    <div class="col-md-6">
    </div>
  </div>
</div>

Step 4: Online User List (Presence)

So now that ChatEngine is connected and set up, let’s start adding some features, beginning with an online user list that updates in realtime.

Inside of the event $.ready, which is executed as soon as ChatEngine is connected and ready to use, we will bind the global channel to a field. The field is defined as chat into our service in order to reach it from whatever component our app takes through of the dependency injection.

import { Injectable } from '@angular/core';
import { ChatEngineCore } from 'chat-engine';

@Injectable()
export class ChatEngine {
  instance: any;
  create: any;
  plugin: any;
  me: any = { state: {} };
  chat: any = {};
  constructor() {
    ...
  }
}
import { Component, OnInit } from '@angular/core';
import { ChatEngine } from './chatEngine';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  private ce: any;

  constructor(private chatEngine: ChatEngine) {
    this.ce = chatEngine;
  }

  ngOnInit() {
    this.ce.instance.connect(new Date().getTime(), {}, 'auth-key');
    this.ce.instance.on('$.ready', (data) => {
      this.ce.me = data.me;

      this.ce.chat = this.ce.instance.global;
    });
  }
}

One of the most important parts of Angular2 is development based on components, where each one has a specific responsibility and capability of reuse. So based on this premise, we create a component to display the list of users online which will be mounted inside of our main layout.

For this, we have to add a new file called app.usersOnline.ts into the folder app.

import { Component } from '@angular/core';
import { ChatEngine } from './chatEngine';

@Component({
  selector: 'app-usersOnline',
  templateUrl: './templates/app.usersOnline.html'
})
export class AppUsersOnlineComponent {
  private ce: any;

  constructor(private chatEngine: ChatEngine) {
    this.ce = chatEngine;
  }

  getUsers(obj) {
    let users: any = [];

    if (obj) {
      Object.keys(obj).forEach((key) => {
        users.push(obj[key]);
      });
    }

    return users;
  }
}

Notice the decorator @component that defines the selector or tag used for mounting the component and the template.

Create a folder named templates and add it the next file app.usersOnline.html.

<div class="card">
  <div class="card-body">
    <h4 class="card-title">ChatEngine</h4>
    <p class="card-text">Your are connected with uuid {{ce.me.uuid}}</p>
    <ul id="online-list" class="list-group list-group-flush">
      <li class="list-group-item" *ngFor="let user of getUsers(ce.chat.users)">
        <a href="">{{user.uuid}}</a>
      </li>
    </ul>
  </div>
</div>

Before using this component, we have to mount it into our ngModule which is defined in the file app.module.ts.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ChatEngine } from './chatEngine';

import { AppComponent } from './app.component';
import { AppUsersOnlineComponent } from './app.usersOnline';

@NgModule({
  declarations: [
    AppComponent,
    AppUsersOnlineComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [ChatEngine],
  bootstrap: [AppComponent]
})
export class AppModule { }

Next, we modify the main layout in order to add the component.

<div class="container-fluid">
  <div class="row">
    <div class="col-md-6">
      <app-usersOnline></app-usersOnline>
    </div>
    <div class="col-md-6">
    </div>
  </div>
</div>

Displaying the identifiers, which are just numbers, doesn’t look great. In this case, we’ll assign a random username using the Random Username plugin. Import the NPM module and load the plugin to the object me, and make a small change in your UI.

For your production app, you’ll obviously use the custom usernames your users choose.

import { Component, OnInit } from '@angular/core';
import { ChatEngine } from './chatEngine';
declare var require: any;
const random = require('chat-engine-random-username');

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  private ce: any;

  constructor(private chatEngine: ChatEngine) {
    this.ce = chatEngine;
  }

  ngOnInit() {
    this.ce.instance.connect(new Date().getTime(), {}, 'auth-key');
    this.ce.instance.on('$.ready', (data) => {
      this.ce.me = data.me;
      this.ce.me.plugin(random());

      this.ce.chat = this.ce.instance.global;
    });
  }
}
<div class="card">
  <div class="card-body">
    <h4 class="card-title">ChatEngine</h4>
    <p class="card-text">Your are <strong>{{ce.me.state.username}}</strong> with uuid {{ce.me.uuid}}</p>
    <ul id="online-list" class="list-group list-group-flush">
      <li class="list-group-item" *ngFor="let user of getUsers(ce.chat.users)">
        <a href="">{{user.state.username}}</a>
      </li>
    </ul>
  </div>
</div>

Now, let’s build the functionality to allow your users to search for other online users. Just like in our previous step, we’re going to use a plugin to implement this feature. In this case, we use the Online User Search plugin.

Import the plugin and load it (in this case into the global chat).

import { Component, OnInit } from '@angular/core';
import { ChatEngine } from './chatEngine';
declare var require: any;
const random = require('chat-engine-random-username');
const search = require('chat-engine-online-user-search');

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  private ce: any;

  constructor(private chatEngine: ChatEngine) {
    this.ce = chatEngine;
  }

  ngOnInit() {
    this.ce.instance.connect(new Date().getTime(), {}, 'auth-key');
    this.ce.instance.on('$.ready', (data) => {
      this.ce.me = data.me;
      this.ce.me.plugin(random());

      this.ce.chat = this.ce.instance.global;
      this.ce.chat.plugin(search({ prop: 'state.username', caseSensitive: false }));
    });
  }
}

Update the template of our component for displaying online users.

<div class="card">
  <div class="card-body">
    <h4 class="card-title">ChatEngine</h4>
    <p class="card-text">Your are <strong>{{ce.me.state.username}}</strong> with uuid {{ce.me.uuid}}</p>
    <ul id="online-list" class="list-group list-group-flush">
      <li class="list-group-item" *ngFor="let user of getUsers(ce.chat.users)">
        <a href="" *ngIf="!user.hideWhileSearch">{{user.state.username}}</a>
      </li>
    </ul>
    <br/>
    <div>
      <div class="input-group">
        <input type="text" class="form-control message" (ngModelChange)="search()"
               [(ngModel)]="mysearch" placeholder="Search for Username"/>
      </div>
    </div>
  </div>
</div>

Notice the template now uses the search method, which is executed when it detects a change in the field mysearch.

import { Component } from '@angular/core';
import { ChatEngine } from './chatEngine';

@Component({
  selector: 'app-usersOnline',
  templateUrl: './templates/app.usersOnline.html'
})
export class AppUsersOnlineComponent {
  private ce: any;
  mysearch: string = '';

  constructor(private chatEngine: ChatEngine) {
    ...
  }

  getUsers(obj) {
    ...
  }

  search() {
    if (this.mysearch.length >= 2) {
      let found = this.ce.chat.onlineUserSearch.search(this.mysearch);

      // hide every user
      for(let uuid in this.ce.chat.users) {
        this.ce.chat.users[uuid].hideWhileSearch = true;
      }

      // show all found users
      for(let i in found) {
        this.ce.chat.users[found[i].uuid].hideWhileSearch = false;
      }
    } else {
      for(let uuid in this.ce.chat.users) {
        this.ce.chat.users[uuid].hideWhileSearch = false;
      }
    }
  }
}

The search method has logic to hide users that don’t match the query (in this case, being offline), through the directive *nglf exposed to the list of users.

 

Step 6: Inviting Users to Chat

Now that we’ve got our user list and can search for users, we need to be able to send invitations to chatrooms.

First, add the field called chats which is an array inside of our service. Then, add a new function called newChat(), which receives the data that representsan user as a parameter.

import { Injectable } from '@angular/core';
import { ChatEngineCore } from 'chat-engine';

@Injectable()
export class ChatEngine {
  instance: any;
  create: any;
  plugin: any;
  me: any = { state: {} };
  chat: any = {};
  chats: any[] = [];
  constructor() {
    ...
  }

  newChat(user) {
    // define a channel
    let chat = new Date().getTime();
    // create a new chat with that channel
    let newChat = new this.instance.Chat(chat);
    // we need to auth ourselves before we can invite others
    newChat.on('$.connected', () => {
      // this fires a private invite to the user
      newChat.invite(user);
      // add the chat to the list
      this.chats.push(newChat);
    });
  }
}

The newChat method is available globally, so we can call it from the component of online users. To invoke this method, we create a new method inside the component which acts as an event handler and is invoked from the template.

import { Component } from '@angular/core';
import { ChatEngine } from './chatEngine';

@Component({
  selector: 'app-usersOnline',
  templateUrl: './templates/app.usersOnline.html'
})
export class AppUsersOnlineComponent {
  private ce: any;
  mysearch: string = '';

  constructor(private chatEngine: ChatEngine) {
    ...
  }

  getUsers(obj) {
    ...
  }

  search() {
    ...
  }

  newChat(user) {
    this.ce.newChat(user);

    return false;
  }
}
<div class="card">
  <div class="card-body">
    <h4 class="card-title">ChatEngine</h4>
    <p class="card-text">Your are <strong>{{ce.me.state.username}}</strong> with uuid {{ce.me.uuid}}</p>
    <ul id="online-list" class="list-group list-group-flush">
      <li class="list-group-item" *ngFor="let user of getUsers(ce.chat.users)">
        <a href="" *ngIf="!user.hideWhileSearch" (click)="newChat(user)">{{user.state.username}}</a>
      </li>
    </ul>
    <br/>
    <div>
      ...
    </div>
  </div>
</div>

Step 7: Accept Invitation to Chat

Now that we know how to send invitations, let’s move onto accepting the invitation.

We first have to add an event handler to the object which represents me inside of ChatEngine.

import { Component, OnInit } from '@angular/core';
import { ChatEngine } from './chatEngine';
declare var require: any;
const random = require('chat-engine-random-username');
const search = require('chat-engine-online-user-search');

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  private ce: any;

  constructor(private chatEngine: ChatEngine) {
    this.ce = chatEngine;
  }

  ngOnInit() {
    this.ce.instance.connect(new Date().getTime(), {}, 'auth-key');
    this.ce.instance.on('$.ready', (data) => {
      this.ce.me = data.me;
      this.ce.me.plugin(random());

      // when I get a private invite
      this.ce.me.direct.on('$.invite', (payload) => {
        const chat = new this.ce.instance.Chat(payload.data.channel);
        chat.onAny((a) => {
          console.log(a);
        });
        // create a new chat and render it in DOM
        this.ce.chats.push(chat);
      });

      this.ce.chat = this.ce.instance.global;
      this.ce.chat.plugin(search({ prop: 'state.username', caseSensitive: false }));
    });
  }
}

Notice that when it’s received, we add the new chat to the array of chats which was introduced in the previous step. This way, we can access it through our service and render it to the HTML.

To get this (following the development using components premise of Angular2), we create two additional components – one for displaying the list of chatrooms and the other for displaying each chat room.

Add the component app.chat.ts to receive data from the parent through the definition of two components of interaction. This is defined with the usage of decorator @input at the beginning of the declaration with the fields: chat and index.

import {Component, Input, OnInit } from '@angular/core';
import {ChatEngine} from './chatEngine';

@Component({
  selector: 'app-chat',
  templateUrl: './templates/app.chat.html'
})
export class AppChatComponent implements OnInit {
  private ce: any;
  @Input() chat: any;
  @Input() index: number;
  users: any[] = [];

  constructor(private chatEngine: ChatEngine) {
    this.ce = chatEngine;
  }

  ngOnInit() {
  }

  getUsers(obj) {
    let users: any = [];

    if (obj) {
      Object.keys(obj).forEach((key) => {
        users.push(obj[key]);
      });
    }

    return users;
  }
}

Add the template app.chat.html of our component into the folders templates. This displays it, including the name of the chat and the list of users contained in it.

<div class="chat col-xs-12">
  <div class="card">
    <div class="card-header">
      <div class="row">
        <div class="col-sm-11">
          {{chat.channel}}
        </div>
        <div class="col-sm-1 text-right">
        </div>
      </div>
    </div>
    <ul class="list-group list-group-flush online-list-sub">
      <li class="list-group-item" *ngFor="let user of getUsers(chat.users)">
        {{user.state.username}}
      </li>
    </ul>
    <div class="card-body">
    </div>
  </div>
</div>

The second component is simple. Its responsibility is to display the list of chat rooms using the component.

For this, we need to add the next file: app.chats.ts and the template app.chats.html.

import {Component} from '@angular/core';
import {ChatEngine} from './chatEngine';

@Component({
  selector: 'app-chats',
  templateUrl: './templates/app.chats.html'
})
export class AppChatsComponent {
  private ce: any;

  constructor(private chatEngine: ChatEngine) {
    this.ce = chatEngine;
  }
}
<div id="chats" class="row" *ngFor="let chat of ce.chats; let i = index">
  <app-chat [chat]="chat" [index]="i"></app-chat>
</div>

To use them, add the components to the list of declarations in our ngModule – defined in the file app.module.ts.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ChatEngine } from './chatEngine';

import { AppComponent } from './app.component';
import { AppUsersOnlineComponent } from './app.usersOnline';
import { AppChatsComponent } from './app.chats';
import { AppChatComponent } from './app.chat';

@NgModule({
  declarations: [
    AppComponent,
    AppUsersOnlineComponent,
    AppChatsComponent,
    AppChatComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [ChatEngine],
  bootstrap: [AppComponent]
})
export class AppModule { }

Once these components are declared, we can add the list of chat rooms into the template app.component with the tag <app-chats>.

<div class="container-fluid">
  <div class="row">
    <div class="col-md-6">
      <app-usersOnline></app-usersOnline>
    </div>
    <div class="col-md-6">
      <app-chats></app-chats>
    </div>
  </div>
</div>

Step 8: Send and Receive Messages

Our chat app is nearly done, but there’s something very important we need to add…sending and receiving messages in realtime!

To start, we have to add the function send, which is executed when the user clicks the button and activates the event message inside the hook ngOnInit. The message will be received as soon as the component has bound all data with the template. 

import {Component, Input, OnInit } from '@angular/core';
import {ChatEngine} from './chatEngine';

@Component({
  selector: 'app-chat',
  templateUrl: './templates/app.chat.html'
})
export class AppChatComponent implements OnInit {
  private ce: any;
  @Input() chat: any;
  @Input() index: number;
  users: any[] = [];
  messages: any[] = [];
  message: string;

  constructor(private chatEngine: ChatEngine) {
    this.ce = chatEngine;
  }

  ngOnInit() {
    this.chat.on('message', (payload) => {
      // if the last message was sent from the same user
      payload.sameUser = this.messages.length > 0 && payload.sender.uuid === this.messages[this.messages.length - 1].sender.uuid;

      // if this message was sent by this client
      payload.isSelf = payload.sender.name === 'Me';

      // add the message to the array
      this.messages.push(payload);
    });
  }

  getUsers(obj) {
    ...
  }

  send() {
    this.chat.emit('message', { text: this.message });
    this.message = '';
  }
}

We also have to introduce additional elements to our template to align with the feature that we just added.

<div class="chat col-xs-12">
  <div class="card">
    ...
    <div class="card-body">
      <div>
        <div *ngFor="let message of messages" [ngClass]="{'text-right': !message.isSelf}">
          <p class="text-muted" [ngClass]="{'d-none': message.sameUser}">{{message.sender.state.username}}</p>
          <p>{{message.data.text}}</p>
        </div>
      </div>
      <div>
        <div class="input-group">
          <input type="text" class="form-control" placeholder="Your Message..." [(ngModel)]="message">
          <span class="input-group-btn">
              <button class="btn btn-primary" type="submit" (click)="send()">Send</button>
            </span>
        </div>
      </div>
    </div>
  </div>
</div>

Step 9: Leave a Chat Room

Now let’s allow users to leave the chatroom, and remove the user from all the user list. We’ll add this function to the chat component to ensure the chatroom disappears from the UI.

import {Component, Input, OnInit } from '@angular/core';
import {ChatEngine} from './chatEngine';

@Component({
  selector: 'app-chat',
  templateUrl: './templates/app.chat.html'
})
export class AppChatComponent implements OnInit {
  private ce: any;
  @Input() chat: any;
  @Input() index: number;
  users: any[] = [];
  messages: any[] = [];
  message: string;

  constructor(private chatEngine: ChatEngine) {
    this.ce = chatEngine;
  }

  ngOnInit() {
    ...
  }

  getUsers(obj) {
    ...
  }

  leave() {
    this.chat.leave();
    this.ce.chats.splice(this.index, 1);

    return false;
  }

  send() {
    ...
  }
}

We have to bind this function to the HTML in order to be invoked when the user wants to close the chatroom.

<div class="chat col-xs-12">
  <div class="card">
    <div class="card-header">
      <div class="row">
        <div class="col-sm-11">
          {{chat.channel}}
        </div>
        <div class="col-sm-1 text-right">
          <a href="" class="close" (click)="leave()">x</a>
        </div>
      </div>
    </div>
    <ul class="list-group list-group-flush online-list-sub">
      ...
    </ul>
    <div class="card-body">
      ...
    </div>
  </div>
</div>

Step 10: Add More Users to the Chatroom

Now that we can invite and accept invitations, let’s add the functionality to the app itself. We will have to add two additional functions to our component, the first one is focusing on searching from the global list and the second one is for inviting users to join the chatroom.

import {Component, Input, OnInit } from '@angular/core';
import {ChatEngine} from './chatEngine';

@Component({
  selector: 'app-chat',
  templateUrl: './templates/app.chat.html'
})
export class AppChatComponent implements OnInit {
  private ce: any;
  @Input() chat: any;
  @Input() index: number;
  users: any[] = [];
  messages: any[] = [];
  message: string;
  mysearch: string = '';

  constructor(private chatEngine: ChatEngine) {
    this.ce = chatEngine;
  }

  ngOnInit() {
    ...
  }

  getUsers(obj) {
    ...
  }

  invite(user) {
    this.chat.invite(user);
    this.users = [];

    return false;
  }

  leave() {
    ...
  }

  send() {
    ...
  }

  search() {
    if (this.mysearch.length >= 2) {
      this.users = this.ce.chat.onlineUserSearch.search(this.mysearch);
    } else {
      this.users = [];
    }
  }
}

To finish this step, we have to bind two functions to the HTML elements which are in charge of invoking these functions.

<div class="chat col-xs-12">
  <div class="card">
    <div class="card-header">
      ...
    </div>
    <ul class="list-group list-group-flush online-list-sub">
      ...
    </ul>
    <div class="card-body">
      ...
      <hr/>
      <div class="card-body">
        <h6>Add a user to this chat</h6>
        <div ng-submit="searchFromGlobal()">
          <div class="input-group">
            <input type="text" class="form-control message"
                   placeholder="Add User" (ngModelChange)="search()" [(ngModel)]="mysearch"/>
          </div>
        </div>
        <ul class="list-group list-group-flush online-list-sub">
          <li class="list-group-item" *ngFor="let user of users">
            <a href="" (click)="invite(user)">{{user.state.username}}</a>
          </li>
        </ul>
      </div>
    </div>
  </div>
</div>

Step 11: Typing Indicators

The last feature we’ll add to our chat application is typing indicators – showing when a user is currently typing. We’ll use the Typing Indicator plugin to implement this.

import {Component, Input, OnInit } from '@angular/core';
import {ChatEngine} from './chatEngine';
declare var require: any;
const typing = require('chat-engine-typing-indicator');

@Component({
  selector: 'app-chat',
  templateUrl: './templates/app.chat.html'
})
export class AppChatComponent implements OnInit {
  ...

  constructor(private chatEngine: ChatEngine) {
    ...
  }

  ngOnInit() {
    this.chat.plugin(typing({ timeout: 5000 }));

    this.chat.on('message', (payload) => {
      ...
    });

    // when we get notified of a user typing
    this.chat.on('$typingIndicator.startTyping', (event) => {
      event.sender.isTyping = true;
    });

    // when we get notified a user stops typing
    this.chat.on('$typingIndicator.stopTyping', (event) => {
      event.sender.isTyping = false;
    });
  }

  ...
}

Once the plugin is loaded inside the component, and the events are mounted, the plugin will be triggered when a user begins typing, and either sends or deletes their message.

<div class="chat col-xs-12">
  <div class="card">
    ...
    <ul class="list-group list-group-flush online-list-sub">
      <li class="list-group-item" *ngFor="let user of getUsers(chat.users)">
        {{user.state.username}}
        <span *ngIf="user.isTyping">is typing...</span>
      </li>
    </ul>
    <div class="card-body">
      ...
      <div>
        <div class="input-group">
          <input type="text" class="form-control" placeholder="Your Message..." [(ngModel)]="message"
                 (ngModelChange)="chat.typingIndicator.startTyping()">
          <span class="input-group-btn">
              <button class="btn btn-primary" type="submit" (click)="send()">Send</button>
            </span>
        </div>
      </div>
      <hr/>
      <div class="card-body">
        ...
      </div>
    </div>
  </div>
</div>

 

Next Steps

You now have a basic but powerful chat app rich with features!

There’s more you can do. Check out our GitHub Repo to see more plugins, supported languages, and other programmatic features powered by ChatEngine.

Use Cases:

Try PubNub Today

Connect up to 100 devices for Free