In this tutorial, we’ll create a one on one chat in Angular using the CometChat Pro SDK. You’ll be able to log in, see the list of contacts and send messages to each other.

At the end of the tutorial, this is how the chat screen will look:

Chat screen

To follow along with the code, see this repository on GitHub.

Hold your horses ⚠️🐴!
To follow this tutorial or run the example source code you'll need to create a V1 application.

v2 will be out of beta soon at which point we will update this tutorial.

Project setup

To get started we need a new Angular project:

ng new AngularOneOnOne --routing --style scss --skip-tests

This will create a new folder with the basic structure of a Hello World application using Angular. It
will install all dependencies and create a local git repository for us.

AngularOneOnOne represents the name of the project, you can use here whatever name suits you. The routing **flag will setup the Angular router inside our project. We use the style flag to specify that we want to use Sass instead of plain CSS.

With the skip-tests we instruct the CLI to omit the creation of any test files. Not something I would recommend for a real app, but in our case, it’s ok for a tutorial.

Components structure

Before I start working on a new project, I take a bit of time and think of the overall structure of the application and what components we might need. Then I create a basic structure of the app from all the components I thought of.

In our case, for beginning, we need at least a login page and a chat page, so we’ll create these two:

ng g component Login —skip-tests


ng g component Chat —skip-tests

Similar to what we did when we created the application, but now we generate components. Angular CLI will create the required files for us and update the AppModule with the new component declarations.

We kept the skip-tests flag, as we don’t want additional test files for these components either.

Angular Material

To make our lives easier, we’ll use Angular Material. A components library created by Google, using the Material Design components, for Angular. It has most of the basic building blocks that we need for an application. Things like buttons, inputs, forms and also a theme system that can be customized easily to our needs.

Follow these steps to set it up. First, we need to install all the required packages:

npm install --save @angular/material @angular/cdk @angular/animations

Then, we need to import the Angular animations module. Inside our app module:

import {BrowserAnimationsModule} from '@angular/platform-browser/animations';

@NgModule({
  ...
  imports: [BrowserAnimationsModule],
  ...
})
export class AppModule { }

If you don’t want to use animations, you can import the NoopAnimationsModule. This will make sure that everything still works, but there will be no animations.

As a best practice, each time I’m using a library, like Angular Material, I’m creating a separate module where I group all the things I use from that library. In this case, I’ve created a new file called material.module.ts and placed inside it all the material modules that I plan to use.

import {
  MatSnackBarModule,
  MatInputModule,
  MatFormFieldModule,
  MatButtonModule
} from '@angular/material';
@NgModule({
  exports: [
    MatInputModule,
    MatFormFieldModule,
    MatButtonModule,
    MatSnackBarModule
  ]
})
export class MaterialModule {}

This module can easily be imported into our main application module. Or in a more complex application in shared or other modules.

Once all this is done, the only thing left for us is to import a theme. In our case, we created a custom CometChat theme that will be used by Angular Material. If you need to generate a theme like this, google material theme generator and there will be plenty of options available.

The theme that we’re using can be found inside the comet-chat-theme.scss file. To use this theme we need to import it in our main styles file (styles.scss):

@import 'styles/comet-chat-theme.scss';

Login page

The most basic thing we need here is an input for the user to specify a username and a button to login. For the tutorial we create a nicely looking form:

<div class="login-page">
  <div class="login-box">
    <div class="login-content">
      <h1>Welcome Back</h1>
      <form (ngSubmit)="login(userId.value)">
        <mat-form-field>
          <input matInput placeholder="User ID" type="email" #userId />
          <mat-error *ngIf="error">
            {{ error }}
          </mat-error>
        </mat-form-field>
        <button mat-raised-button color="primary">LOG IN</button>
      </form>
      <div>
        <span>Don't have an account?</span>
        <a routerLink="/signup">Sign up</a>
      </div>
    </div>
    <img src="assets/chat-illustration-1.svg" />
  </div>
</div>

Besides all the bells and whistles we have an input for the user names and a button to click. You can find all the assets that were used inside the GitHub repo under the assets folder.

All the styles associated with a component can also be found in the linked scss file. For example, the one for the login component is here.

We also have an error component that we use to show what went wrong when performing the login. We’ll see in the component logic how we use this.

We have a login method that is called with the value of the user id field using a template reference. We’ll implement this method inside the component a bit later on, once we have everything else in place.

This is how the login page looks:

Login screen

To be able to implement our component functionality we need to create two services: an authentication service and a guard to redirect to our component. Again, we generate them using the Angular CLI:

ng g service core/auth —skip-tests


ng g guard chat/chat —skip-tests

For a better organization, I placed the authentication service inside a core folder and our guard inside the chat folder, as this is what it will guard 🙂.

Authentication service

The authentication service has to perform two tasks:

  • initialize our application
  • login the user

Before any methods from the CometChat API can be used, we need to call the init method. The init method and the login method will need an App Id. We can call the method in the constructor:

constructor(private snackBar: MatSnackBar) {
    this.init(environment.appId);
}

And the init method implementation would look something like this:

init(appId: string, apiKey: string) {
 CometChat.init(appId).then(
      msg => console.log('Initialized succesfull: ', msg),
      err => {
        console.log('App init failed', err);
        this.snackBar.open(
          'App initialization failed. Please refresh the page.'
        );
      }
    );
}

We also have to import CometChat so we’re able to use all the needed methods:

import { CometChat } from '@cometchat-pro/chat';

The CometChat package now comes with built-in typings, so we’ll get auto-completion and additional help when writing code. Inside the init method, we call the CometChat.init function with the app id as an argument. In case of success, we store the app id and API key in local storage. If it’s successful, it means they are correct and we can use them. In case of an error, we show the user a message that the app initialization failed. For this, we use the Snackbar component from Angular Material.

You can see more about how to set up a new application with the CometChat Javascript SDK in the documentation.

The second thing needed here is the authentication of the user. For the purpose of this tutorial we’ll authenticate the user only with the user name, using the login method:

login(userId: string) {
  return CometChat.login(userId)
    .then(usr => (this.currentUser = usr), (this.currentUser = null))
    .then(_ => console.log('User logged in'), console.error);
}

Most of the methods from CometChat return a promise and it’s important to return it inside our function to be able to handle them in the place where the method is called. Even if the action is handled in the method itself. For example, we want to wait for a result from the login method before we do something else.

We have a local current user variable declared:

currentUser: CometChat.UserObj;

And if the operation is successful, we assign the current user to the result, otherwise, we make sure it’s null.

Now with this in place, we can implement the login method inside our login component:

constructor(readonly router: Router, readonly auth: AuthService) {}
  
login(userId: string) {
  this.auth
    .login(userId)
    .then(
      () => this.router.navigateByUrl('/chat'),
      err => (this.error = err)
    );
}

If the operation is successful we navigate to the chat screen. If not, we show an error message.

If you need additional authentication methods or want to see what other options there are, you can check the CometChat documentation.

Routing

The login page will be the first thing a not authenticated user will see inside our application. So we need to set up the routing in our application accordingly. We can create a new Angular module to handle just the routing. This is another best practice I’ve caught while developing Angular applications.

ng g module app-routing

And now, we can setup the routes:

const routes: Routes = [
  {
    path: 'login',
    component: LoginComponent
  },
  {
    path: '',
    redirectTo: 'login',
    pathMatch: 'full'
  },
  {
    path: 'chat',
    component: ChatComponent,
    canActivate: [ChatGuard]
  },
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

The login path is simple enough, it will load the login component. We also create a redirect path for an empty URL, to go to our login component.

Now, for the chat component, we want to load it only if the user is authenticated, otherwise, we want to show the login. So we use the canActivate property where we add the guard we just created.

For the guard implementation:

@Injectable({
  providedIn: 'root'
})
export class ChatGuard implements CanActivate {
  constructor(readonly auth: AuthService, readonly router: Router) {}

  canActivate() {
    if (!this.auth.currentUser) {
      this.router.navigateByUrl('/login');
      return false;
    }
    return true;
  }
}

If there is no current user, it means we’re not authenticated, so we navigate to log in, otherwise, everything is in place and we can load the chat component.

Chat component

Here we take a break from writing code and think about the structure again. We have a chat component that can be split into a contacts list and a messages view, so we can create these components:

ng g component chat/contacts-list —skip-tests


ng g component chat/messages-view —skip-tests

Since they are children of the chat component and the only place where they will be used inside the application, I’ve placed them inside the chat folder.

Now that we have the component, we can create the basic layout for the chat page. And this has two parts, the toolbar, from the top, with the current user and some graphic elements:

<div class="chat-toolbar">
  <div class="logo">
    <img src="assets/chat-logo.svg" />
    <span>Chat</span>
  </div>
  <div *ngIf="authService.currentUser" class="user-profile">
    <span>
      Welcome, <b> {{ authService.currentUser.name }}</b>
    </span>
    <img [src]="authService.currentUser.avatar"/>
  </div>
</div>

Inside the toolbar, we show the name of the current user, which can be retrieved from the authentication service that we previously created.

And the actual chat page, where we place the contacts list together with the two elements we just created:

<div class="chat-page">
  <div class="chat-box">
    <app-contacts-list
      (userSelected)="onUserSelected($event)"
    ></app-contacts-list>
    <app-messages-view
      [messages]="messages"
      (sendMessage)="onSendMessage($event)"
    ></app-messages-view>
  </div>
</div>

We assume that we’ll have some inputs and outputs on the contacts list and messages view components. Along with some handlers inside the chat component itself. We’ll create those and everything else that’s required in the next part of the tutorial.

Contacts service

To retrieve and manage the list of contacts/users we need a new service:

ng g service chat/contacts-list/contacts —skip-tests

We’ll use it to fetch and hold the list of contacts, so we need a variable for that:

contacts: CometChat.UserObj[];

Using the UserRequestBuilder from CometChat SDK we can create a request and retrieve the list with all the users:

getContacts() {
  const usersRequest = new CometChat.UsersRequestBuilder()
    .setLimit(USERS_TO_FETCH)
    .build();

  return usersRequest.fetchNext().then(users => this.contacts = users);
}

Users to fetch is a constant that we defined in our file. In a real-world application you might want a larger value for this:

const USERS_TO_FETCH = 30;

Now, whenever a user will switch to connected or disconnected mode, we want to get a notification so we can update our component accordingly. For this we add a new user listener and handle these two events:

trackOnlineStatus(listenerId: string) {
  const userListener = new CometChat.UserListener({
    onUserOffline: usr => this.setUserStatus(usr, 'offline'),
    onUserOnline: usr => this.setUserStatus(usr, 'online')
  });

  return CometChat.addUserListener(listenerId, userListener);
}

The setUserStatus method will find the required contact and change the status to what we specify:

private setUserStatus(usr: CometChat.UserObj, status: string) {
  if (!this.contacts) {
    return;
  }
  const userToUpdate = this.contacts.find(c => c.uid === usr.uid);
  if (userToUpdate) {
    userToUpdate.status = status;
  }
}

Everything is in place here to create the contacts list component.

For a detailed overview of all the options related to users, you can read the CometChat documentation here.

Contacts list component

In here we want to show a list with all the contacts, and react when the user clicks on one of them. That one should become active contact.

Inside the template we create a list and we loop through all the users from the contacts service, displaying all the relevant information:

<div class="main">
  <div class="contacts-title">Contacts</div>
  <ul>
    <li
      *ngFor="let contact of contactsService.contacts"
      (click)="onUserSelected(contact)"
      [ngClass]="{ active: contact.uid === activeUser?.uid }"
    >
      <div class="image-container">
        <img [src]="contact.avatar"/>
        <span
          class="circle"
          [ngClass]="contact.status === 'online' ? 'on' : 'off'"
        ></span>
      </div>
      <div class="contacts-info">
        <span class="status-title">{{ contact.name }}</span>
        <span class="status-text">{{ contact.statusMessage }}</span>
      </div>
    </li>
  </ul>
</div>

We handle the event when the list item is clicked, to mark that contact as selected and we add a class to it only when it’s selected, so it’s easily visible which contact is selected right now.

For the list item content, we show the contacts avatar, name and status message, if any. We also show a small circle based on the status of the contact, if it’s online or offline.

Inside the component’s class, we want to do several things, first, we need to get an instance of the contacts service:

constructor(readonly contactsService: ContactsService) {}

Then we need to read all the users, track the status changes and select the first one from the list. We do all this inside the onInit life cycle hook:

async ngOnInit() {
  await this.contactsService.getContacts();
  await this.contactsService.trackOnlineStatus(listenerId);
   
  this.selectFirstContact();
}

Next, we want to handle the event of a contact being selected:

@Output() userSelected = new EventEmitter<CometChat.User>();
activeUser: CometChat.User;

onUserSelected(user: CometChat.User) {
  this.activeUser = user;
  this.userSelected.emit(user);
}

We have an Output to be able to tell other components that a new user was selected, and a local variable to hold this user. We used it inside the template to figure out which one to style differently. It might also be useful later when we want to send a message to that user 🙂

Chat service

To be able to implement the messages view, we need a chat service. We’ll group here all the methods related to messages, like sending and receiving messages.

As before, we start by creating the service:

ng g service chat/chat —skip-tests

To send messages, we first compose a TextMessage, for the purpose of this tutorial, this is the only type of message that we support. Then we send is using the sendMessage method from CometChat:

sendMessage(receiverId: string, text: string) {
  const message = new CometChat.TextMessage(
    receiverId,
    text,
    CometChat.MESSAGE_TYPE.TEXT,
    CometChat.RECEIVER_TYPE.USER
  );

  return CometChat.sendMessage(message);
}

Whenever we select a contact, we also want to retrieve previous messages that we exchanged with him. For this we use the MessageRequestBuilder class to fetch those:

getPreviousMessages(userId: string) {
  const messageRequest = new CometChat.MessagesRequestBuilder()
    .setUID(userId)
    .setLimit(100)
    .build();

  return messageRequest.fetchPrevious();
}

We might also want to listen to live messages that will be incoming and show them. For this we need a message listener:

listenForMessages(listenerId: string, onMessageReceived: (msg: any) => void) {
  CometChat.addMessageListener(
    listenerId,
    new CometChat.MessageListener({
      onTextMessageReceived: onMessageReceived,
      onMediaMessageReceived: _ => undefined
    })
  );
}

And to be good citizens and cleanup after ourselves, we also need a method to remove the listener that we just added when we’re not using it anymore:

removeMessageListener(listenerId: string) {
  CometChat.removeMessageListener(listenerId);
}

Everything needed is in place, so we can move to create the messages view component.

As before, to see what other methods and arguments are available to us, check the documentation on sending and receiving messages.

Messages view component

This will be a view with an input and a list of messages exchanged between those users.
Whenever we don’t have any messages written, we’ll show a nice artwork to notify everyone about this:

<ng-template #noMessages>
  <div class="no-messages">
    <img src="/assets/no-messages-illustration.svg" />
    <span class="title">No new message?</span>
    <span class="info">Send your first message below.</span>
  </div>
</ng-template>

The beautiful artwork when there are no messages:

No messages

And now the container for the messages themselves:

<div class="main">
  <div class="user-info">{{ authService?.currentUser.name }}</div>
  <div
    *ngIf="messages && messages[0]; else noMessages"
    class="messages-container"
    #scrollMe
    >
    <ul>
      <li *ngFor="let message of messages">
        <img [src]="message.sender.avatar"/>
        <span>{{ message.text }}</span>
      </li>
    </ul>
  </div>
  <div class="input-container">
    <input
      placeholder="Type something"
      #messageInput
      (keydown.enter)="onSendMessage(messageInput.value); messageInput.value = ''"/>
  </div>
</div>

I’ve stripped away some of the code needed for styling, so it’s easier to understand. For a more complete version, see the one on GitHub.

For the messages, we have a list, where we look through all of them and we show the avatar and the message text. The most complex part here would be the styling. Check out the associated scss file for some tips on how you can achieve this. But I’m no CSS expert, so I’m sure you’ll be able to find better and nicer options to achieve the same output.

In the bottom part of the component, we have an input where the user will be able to write a message and send it.

Inside the component class, we don’t have much, an Input for the messages array and an output for the text message to be sent:

@Input() messages: CometChat.TextMessage[] | null;
@Output() sendMessage = new EventEmitter<string>();

constructor(readonly authService: AuthService) {}

onSendMessage(message: string) {
  this.sendMessage.emit(message);
}

This is a classic example of a UI component. It does not know too much about the logic in our application, it has an input and output and it knows how to show the data and react to new messages. To make it purer we could have also sent the current user as input also and then we could have removed the dependency on the authentication service.

When we get new messages inside this component we want to scroll to the bottom of the messages container. And this is not as easy as it sounds. For that we had in the template the #scrollMe template reference.

We first need to get a hold of the reference in our class:

@ViewChild('scrollMe', { static: false })
messagesContainer: ElementRef<HTMLDivElement>;

Then, whenever we get new messages we want to scroll into view the last message. So we implement OnChanges in our component:

ngOnChanges(changes: SimpleChanges): void {
  if (changes.messages) {
    timer(10).subscribe(() => this.scrollIntoView());
  }
}

If we got new messages, we call a method to scroll into view. But we do this using a timer from RxJs to be sure everything is updated and done on the UI before we scroll. This is one possible workaround/solution for this scroll into view problem.
The scroll method uses the element reference to set the scroll top property to the scroll height. This will trigger the element to be scrolled into view:

private scrollIntoView() {
  if (this.messagesContainer) {
    const { nativeElement } = this.messagesContainer;
    nativeElement.scrollTop = nativeElement.scrollHeight;
  }
}

Putting everything together

Now we can hook everything together. The chat component was looking like this:

<div class="chat-page">
  <div class="chat-box">
    <app-contacts-list
      (userSelected)="onUserSelected($event)"
    ></app-contacts-list>
    <app-messages-view
      [messages]="messages"
      (sendMessage)="onSendMessage($event)"
    ></app-messages-view>
  </div>
</div>

So we need to read the list of messages, send a message and handle the case when a new contact is selected.

First we need some variables to hold data and to get an instance of the chat and authentication services:

selectedUser: CometChat.UserObj;
messages: CometChat.TextMessage[] | null = null;

constructor(
  readonly authService: AuthService,
  readonly chatService: ChatService
) {}

Next, when the component is initialized, we need to listen for any incoming messages:

ngOnInit() {
  this.chatService.listenForMessages(listenerId, msg => {
    this.messages = [...this.messages, msg];
  });
}

And when the component is destroyed, we need to remove that listener:

ngOnDestroy() {
    this.chatService.removeMessageListener(listenerId);
  }

Now, we have all live messages inside our view. Whenever a user is selected, we need to hold that user and read any previous messages:

async onUserSelected(usr: CometChat.UserObj) {
  this.selectedUser = usr;
  this.messages = await this.chatService.getPreviousMessages(usr.uid);
}

All messages are handled now and there is one more thing remaining.
We need to be able to send a message:

async onSendMessage(message: string) {
  const sentMessage = await this.chatService.sendMessage(
    this.selectedUser.uid,
    message
  );

  if (sentMessage) {
    this.messages = [...this.messages, sentMessage];
  }
}

If the message was sent successfully, we have to add it to the list of current messages, so it’s displayed on the screen.

Conclusion

Through this tutorial, we went from a new blank Angular app to a full-blown chat service using the CometChat SDK. And it was not that complicated or time-consuming 🙂 . Feel free to use any of the samples in your application or to reach out if you have any questions.

We built a nice and functional Angular chat application using the CometChat SDK. As a bonus inside the GitHub repository you can also find a signup component, ready to be used, although it has no functionality implemented:

Create account screen