loading...
TalkJS

Add chat into your Angular app with TalkJS - Part 2

sarah_chima profile image Sarah Chima ・10 min read

In the first part of this tutorial, we saw how you can add a chat popup to an existing Angular app using TalkJS. In this tutorial, we discuss how we can add the TalkJS Chatbox and Inbox to an Angular app. Let's start with the Chatbox.

Chatbox

In this section, we're going to make sure that a user is able to send a vendor a message from his/her profile page, by the use of a Chatbox. You’ll notice that the code to do this is extremely similar to the code used for creating a Popup.

This is what a Chatbox looks like:
Chatbox image

TalkService

Add the following method to the TalkService:

async createChatbox(otherApplicationUser: User) : Promise {
   const session = await this.currentSessionDeferred.promise;
   const conversationBuilder = await this.getOrCreateConversation(session, otherApplicationUser);
   return session.createChatbox(conversationBuilder);
}

User Profile Component

Navigate to the UserProfileComponent:
src/app/users/components/user-profile/user-profile.component.ts

Template
The first thing we’ll have to do is add a container for the Chatbox in the UserProfileComponent’s template.

Add the following highlighted code to the UserProfileComponent's template:

<div id="chat-container">
      <div class="row">
            <div class="col-sm-4"></div>
            <div class="col-sm-4 container-title">Chat</div>
            <div class="col-sm-4"></div>
      </div>
      <div id="talkjs-container">Loading chat with {{user?.username}}...</div>
</div>

The code that we've just added is an HTML div element. This element is going to serve as the container for our TalkJS Chatbox, which is why we’re calling it talkjs-container.

Template Styling
We’ll have to add some styling to assure that our Chatbox is being shown in the center of the page and is of sufficient height.

Open the styling file for the UserProfileComponent's template: user-profile.component.css.

Add the following code:

#talkjs-container {
   height: 505px;
   margin-top: 1%;
   text-align: center;
}

Component
We’ll now have to add a property and a method in the UserProfileComponent to load the Chatbox in the template’s container.

Add the following property:

import * as Talk from "talkjs";

...
export class UserProfileComponent implements OnInit {
   private chatbox: Talk.Chatbox;
}

Add the following method:

private async loadChatbox(otherUser: User) {
   this.chatbox = await this.talkService.createChatbox(otherUser);
   this.chatbox.mount(document.getElementById('talkjs-container'));
}

Add the method call to the ngOnInit lifecycle hook:

ngOnInit() {
   this.userService.getUser(this.getUserId()).then(user => {
      this.user = user;
      this.loadChatbox(this.user);
   });
}

When our component is being destroyed, we should make sure that the TalkJS Chatbox and its event listeners are also being destroyed. Add the following body to the ngOnDestroy lifecycle hook:

ngOnDestroy() {
   if (this.chatbox) {
      this.chatbox.destroy();
   }
}

We have now successfully added the TalkJS Chatbox to our application.

If you have successfully executed all steps, your TalkService, UserProfileComponent, UserProfileComponent’s template, and UserProfileComponent’s template styling should look like:

TalkService:

import { Injectable } from "@angular/core";
import * as Talk from 'talkjs';
import { User } from "src/app/shared/models/user.model";
import { AuthenticationService } from "src/app/core/services/authentication.service";
import { Deferred } from "src/app/shared/utils/deffered.util";

@Injectable({
providedIn: 'root'
})
export class TalkService {
   private static APP_ID = 'YOUR_APP_ID';
   private currentTalkUser: Talk.User;
   private currentSessionDeferred = new Deferred()

   constructor(private authenticationService: AuthenticationService) { }

   async createCurrentSession() {
      await Talk.ready;

      const currentUser = await this.authenticationService.getCurrentUser();
      const currentTalkUser = await this.createTalkUser(currentUser);
      const session = new Talk.Session({
         appId: TalkService.APP_ID,
         me: currentTalkUser
      });

      this.currentTalkUser = currentTalkUser;
      this.currentSessionDeferred.resolve(session);
   }

   async createTalkUser(applicationUser: User) : Promise {
      await Talk.ready;

      return new Talk.User({
         id: applicationUser.id,
         name: applicationUser.username,
         photoUrl: applicationUser.profilePictureUrl
      });
   }

   async createPopup(otherApplicationUser: User, keepOpen: boolean) : Promise {
      const session = await this.currentSessionDeferred.promise;
      const conversationBuilder = await this.getOrCreateConversation(session, otherApplicationUser);
      const popup = session.createPopup(conversationBuilder, { keepOpen: keepOpen });

      return popup;
   }

   async createChatbox(otherApplicationUser: User) : Promise {
      const session = await this.currentSessionDeferred.promise;
      const conversationBuilder = await this.getOrCreateConversation(session, otherApplicationUser);

      return session.createChatbox(conversationBuilder);
   }

   private async getOrCreateConversation(session: Talk.Session, otherApplicationUser: User) {
      const otherTalkUser = await this.createTalkUser(otherApplicationUser);

      const conversationBuilder = session.getOrCreateConversation(Talk.oneOnOneId(this.currentTalkUser, otherTalkUser));
      conversationBuilder.setParticipant(this.currentTalkUser);
      conversationBuilder.setParticipant(otherTalkUser);

      return conversationBuilder;
   }
}

UserProfileComponent:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

import * as Talk from "talkjs";

import { UserService } from 'src/app/core/services/user.service';
import { User } from 'src/app/shared/models/user.model';
import { TalkService } from 'src/app/core/services/talk.service';

@Component({
selector: 'app-user-profile',
templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.css']
})
export class UserProfileComponent implements OnInit {
   private chatbox: Talk.Chatbox;
   user: User;

   constructor(
   private userService: UserService,
   private talkService: TalkService,
   private route: ActivatedRoute) { }

   ngOnInit() {
      this.userService.getUser(this.getUserId()).then(user => {
      this.user = user;
      this.loadChatbox(this.user);
      });
   }

   ngOnDestroy() {
      if (this.chatbox) {
         this.chatbox.destroy();
      }
   }

   private async loadChatbox(otherUser: User) {
      this.chatbox = await this.talkService.createChatbox(otherUser);
      this.chatbox.mount(document.getElementById('talkjs-container'));
   }

   private getUserId() {
      return Number(this.route.snapshot.paramMap.get('id'));
   }
}

UserProfileComponent’s template:

<div *ngIf="user">
  <div id="personal-information-container">
      <div class="row">
          <div class="col-sm-4"></div>
          <div class="col-sm-4">
              <div class="card personal-information-card">
                  <img class="card-img-top personal-information-card-img-top img-fluid mx-auto d-block" src="{{user?.profilePictureUrl}}" alt="profile-picture">
                  <div class="card-block">
                      <h4 class="card-title personal-information-card-title">{{user?.username}}</h4>
                  </div>
              </div>
          </div>
          <div class="col-sm-4"></div>
      </div>
  </div>
  <hr class="divider" *ngIf="(user.products) && (user.products.length > 0)">
  <div id="owned-products-container" class="container" *ngIf="(user.products) && (user.products.length > 0)">
    <div class="row">
            <div class="col-sm-4"></div>
            <div class="col-sm-4 container-title">Products</div>
            <div class="col-sm-4"></div>
      </div>
      <div id="products-row" class="row">
        <div class="col-sm-3" *ngFor="let product of user.products">
            <div class="card owned-products-card">
                <img class="card-img-top owned-products-card-img-top img-fluid" src="{{product?.pictureUrl}}" alt="product-image">
                <div class="card-block">
                    <h4 class="card-title owned-products-card-title">{{product?.name}}</h4>
                </div>
            </div>
        </div>
      </div>
  </div>
  <hr class="divider">
  <div id="chat-container">
      <div class="row">
            <div class="col-sm-4"></div>
            <div class="col-sm-4 container-title">Chat</div>
            <div class="col-sm-4"></div>
      </div>
      <div id="talkjs-container">Loading chat with {{user?.username}}..</div>
  </div>
</div>

UserProfileComponent’s template styling:

#talkjs-container {
   height: 505px;
   margin-top: 1%;
   text-align: center;
}

Inbox

Finally, let’s make sure that our user is able to view and send messages in previous conversations by the use of the Inbox.

This is what an Inbox looks like:

Inbox image

TalkService

Add the following code to the TalkService:

async createInbox() : Promise {
   const session = await this.currentSession;

   return session.createInbox();
}

Inbox Component

It’s best practice to display the TalkJS Inbox on a separate page in your application. This means that you’ll have to add a new Component to your application.

You can take a look at how we added an InboxComponent by looking into the source code of this tutorial’s final product, the marketplace with chat functionalities.

Template
We’ll have to add a container for the Inbox in the InboxComponent's template.

Open the InboxComponent's template:
src/app/chat-inbox/components/inbox/inbox.components.html

Add the following code to the template:

<div id="talkjs-container">Loading chats..</div>

Template Styling
We’ll have to add some styling to assure that our Inbox is being shown in the center of the page and is of sufficient height.

Add the following code to the InboxComponent's styling file:

#talkjs-container {
   height: 505px;
   margin-top: 5%;
   text-align: center;
}

Component
We still have to add a method in the InboxComponent that will load our Inbox.

Add the following highlighted property and method:

import * as Talk from "talkjs";

export class InboxComponent implements OnInit {
   private inbox: Talk.Inbox;

   private async createInbox() {
      this.inbox = await this.talkService.createInbox();
      this.inbox.mount(document.getElementById('talkjs-container'));
   }
}

Add the method call in the ngOnInit lifecycle hook:

ngOnInit() {
   this.createInbox();
}

We should make sure to destroy the Inbox and its event handlers when the component gets destroyed:

ngOnDestroy() {
   if (this.inbox) {
      this.inbox.destroy();
   }
}

We have now successfully added the TalkJS Inbox to our application.

If you have successfully executed all steps, your TalkService, InboxComponent, InboxComponent's template, and InboxComponent’s template styling should look like:

TalkService:

import { Injectable } from "@angular/core";
import * as Talk from 'talkjs';
import { User } from "src/app/shared/models/user.model";
import { AuthenticationService } from "src/app/core/services/authentication.service";
import { Deferred } from "src/app/shared/utils/deffered.util";

@Injectable({
providedIn: 'root'
})
export class TalkService {
   private static APP_ID = 'YOUR_APP_ID';
   private currentTalkUser: Talk.User;
   private currentSessionDeferred = new Deferred()

   constructor(private authenticationService: AuthenticationService) { }

   async createCurrentSession() {
      await Talk.ready;

      const currentUser = await this.authenticationService.getCurrentUser();
      const currentTalkUser = await this.createTalkUser(currentUser);
      const session = new Talk.Session({
         appId: TalkService.APP_ID,
         me: currentTalkUser
       });

      this.currentTalkUser = currentTalkUser;
      this.currentSessionDeferred.resolve(session);
   }

   async createTalkUser(applicationUser: User) : Promise {
      await Talk.ready;

      return new Talk.User({
         id: applicationUser.id,
         name: applicationUser.username,
         photoUrl: applicationUser.profilePictureUrl
      });
   }

   async createPopup(otherApplicationUser: User, keepOpen: boolean) : Promise {
      const session = await this.currentSessionDeferred.promise;
      const conversationBuilder = await this.getOrCreateConversation(session, otherApplicationUser);
      const popup = session.createPopup(conversationBuilder, { keepOpen: keepOpen });

      return popup;
   }

   async createChatbox(otherApplicationUser: User) : Promise {
      const session = await this.currentSessionDeferred.promise;
      const conversationBuilder = await this.getOrCreateConversation(session, otherApplicationUser);

      return session.createChatbox(conversationBuilder);
   }

   async createInbox() : Promise {
      const session = await this.currentSessionDeferred.promise;
      return session.createInbox();
   }

   private async getOrCreateConversation(session: Talk.Session, otherApplicationUser: User) {
      const otherTalkUser = await this.createTalkUser(otherApplicationUser);

      const conversationBuilder = session.getOrCreateConversation(Talk.oneOnOneId(this.currentTalkUser, otherTalkUser));
      conversationBuilder.setParticipant(this.currentTalkUser);
      conversationBuilder.setParticipant(otherTalkUser);

      return conversationBuilder;
   }
}

InboxComponent:

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

import * as Talk from "talkjs";

import { TalkService } from 'src/app/core/services/talk.service';

@Component({
selector: 'app-inbox',
templateUrl: './inbox.component.html',
styleUrls: ['./inbox.component.css']
})
export class InboxComponent implements OnInit {
   private inbox: Talk.Inbox;

   constructor(private talkService: TalkService) { }

   ngOnInit() {
      this.createInbox();
   }

   ngOnDestroy() {
      if (this.inbox) {
         this.inbox.destroy();
      }
   }

   private async createInbox() {
      this.inbox = await this.talkService.createInbox();
      this.inbox.mount(document.getElementById('talkjs-container'));
   }
}

InboxComponent's template:

<div id="talkjs-container">Loading chats..</div>

InboxComponent’s template styling:

#talkjs-container {
   height: 505px;
   margin-top: 5%;
   text-align: center;
}

Identity Verification

Before publishing your application, you need to ensure that Identity Verification is enabled to prevent malicious users from hijacking accounts. This requires adding a few lines of code to your backend, which is outside the scope of this tutorial.

Read more about Identity Verification.

Finishing touches

Congratulations, you have implemented TalkJS into an existing application! However, if you want, you can add some finishing touches to make the user experience better.

Enabling file & location sharing

In this chapter, we’re going to allow our users to share both files and their location in any chat.

TalkJS Dashboard
The first thing we have to do is create a custom configuration in the TalkJS dashboard.

Log into the TalkJS dashboard and navigate to the configurations section.

Create a new configuration by clicking on the plus button. You can give the configuration any name, we're going for ‘demo_default’.

We’re able to enable both file- and location sharing by enabling their checkboxes.

Enable the following checkboxes:

TalkService
To enable the configuration that we just created for all our users, all we have to do is add this configuration to the TalkService#createTalkUsermethod.

Add the following highlighted code to TalkService#createTalkUser:

async createTalkUser(applicationUser: User) : Promise {
   await Talk.ready;

   return new Talk.User({
      id: applicationUser.id,
      name: applicationUser.username,
      photoUrl: applicationUser.profilePictureUrl,
      configuration: 'demo_default'
   });
}

Make sure to use the configuration name that you chose yourself in the TalkJS dashboard.

You have now successfully enabled file- and location sharing in your application.

Enabling email and SMS notifications

Enabling email and SMS notifications is really easy within TalkJS. All you have to do is pass TalkJS the users' phone number and/or email address and TalkJS will handle the rest!

Add the following highlighted code to TalkService#createTalkUser:

async createTalkUser(applicationUser: User) : Promise {
   await Talk.ready;

   return new Talk.User({
      id: applicationUser.id,
      name: applicationUser.username,
      photoUrl: applicationUser.profilePictureUrl,
      email: 'youruser@youruseremail.com',
      phone: 'yourusersphone'
});
}

Read more about notifications.

Welcome message

We’re going to add personal welcome messages for each user in our application.

ChatPreferences
Navigate to the ChatPreferences model:
src/app/shared/models/chat-preferences.model.ts

Add a new property for the welcome message as follows:

export class ChatPreferences {
   chatButtonColorHex: string;
   chatWelcomeMessage: string;

   constructor(..., chatWelcomeMessage: string) {
      this.chatButtonColorHex = chatButtonColorHex;
      this.chatWelcomeMessage = chatWelcomeMessage;
   }
}

Mock users
Open the user mocks: src/core/mocks/users.mock.ts

Make sure to add a welcome message for each mock user as follows:

new User(4, 'John', '../../../assets/images/users/john.jpg', new ChatPreferences("#1D1F1E", 
"Hi! Any questions? Let me know how I can help"))

TalkService
Add the following highlighted code to TalkService#createTalkUser:

async createTalkUser(applicationUser: User) : Promise {
   await Talk.ready;

   return new Talk.User({
      id: applicationUser.id,
      name: applicationUser.username,
      photoUrl: applicationUser.profilePictureUrl,
      configuration: "demo_default",
      welcomeMessage: applicationUser.chatPreferences.chatWelcomeMessage
   });
}

You have now successfully added welcome messages to your application.

Destroying Popups

You may have noticed that if you open a Popup with a vendor and then navigate to the Inbox page, the Popup is still visible. If the user is at the Inbox page, there is no need to have any Popups open as these conversations can be opened through the Inbox itself.

We will, therefore, write some code that will make sure that any active Popups will be destroyed whenever the Inbox page is being visited by our user.

Let’s go ahead and open the TalkService.

We’ll have to save all the Popups that are being opened until they're destroyed.
To accomplish this, we’ll first have to add a local variable to the TalkService:

private loadedPopups: Talk.Popup[];

And then we’ll have to make sure the array is being initialized, by adding it’s initialization to the TalkService's constructor:

constructor(private authenticationService: AuthenticationService) {
   this.loadedPopups = [];
}

We should now make sure that every Popup that's being opened, is added to the list of loaded Popups.
Add the following highlighted code to the TalkService#createPopup method:

async createPopup(otherApplicationUser: User, keepOpen: boolean) : Promise {
   const session = await this.currentSessionDeferred.promise;
   const conversationBuilder = await this.getOrCreateConversation(session, otherApplicationUser);
   const popup = session.createPopup(conversationBuilder, { keepOpen: keepOpen });
   this.loadedPopups.push(popup);

   return popup;
}

The last thing we should do in the TalkService now, is make a method that actually destroys all the loaded Popups.
Add the following method:

destroyAllLoadedPopups() {
   if (this.loadedPopups.length > 0) {
      this.loadedPopups.forEach(p => p.destroy());
      this.loadedPopups = [];
   }
}

Add the following method call to the InboxComponent#createInbox:

private async createInbox() {
   this.inbox = await this.talkService.createInbox();
   this.inbox.mount(document.getElementById('talkjs-container'));

   this.talkService.destroyAllLoadedPopups();
}

Final Words
I hope you found this tutorial helpful in adding chat to your Angular app.

There are a lot more things you can customize about TalkJS, such as custom chat UI themes, different languages and so on. What is more? You can take a look at our documentation to read more about our customization possibilities.

If you have a question, don't hesitate to drop by our support chat.

Posted on by:

sarah_chima profile

Sarah Chima

@sarah_chima

A Frontend web developer interested in making the web accessible for everyone.

TalkJS

Build an integrated chat feature in hours instead of months.

Discussion

markdown guide