Azure Communication Chat Using Angular

In this article, we will learn the azure communication chat using Angular and generate tokens in the back-end ASP.Net Core.

First of all, we have to create a communication service resource from here and keep it private.

azure resource connection string

 

Backend:

After generating the resource connection string, we have to create 2 APIs in the backend for token generation.

  1. Create a User and generate a token
  2. Generate token from User Id

 

Install the following NuGet packages:

  • Azure.Communication.Common
  • Azure.Communication.Identity

 

Add connection string in appsettings.json :

 

"ResourceConnectionString": "endpoint=https://test.westus.communication.azure.com/;accesskey=SAMPLE KEY 1234"

 

Add new API Controller :

using System;
using System.Threading.Tasks;
using Azure;
using Azure.Communication;
using Azure.Communication.Identity;
using Azure.Core;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;

namespace Calling
{
    public class UserTokenController : Controller
    {
        private readonly CommunicationIdentityClient _client;

        public UserTokenController(IConfiguration configuration)
        {
            _client = new CommunicationIdentityClient(configuration["ResourceConnectionString"]);
        }

        /// <summary>
        /// Create User and Generate token
        /// </summary>
        /// <returns></returns>
        [Route("/token")]
        [HttpGet]
        public async Task<IActionResult> GetAsync()
        {
            try
            {
                Response<CommunicationUserIdentifierAndToken> response = await _client.CreateUserAndTokenAsync(scopes: new[] { CommunicationTokenScope.Chat });

                var responseValue = response.Value;

                var jsonFormattedUser = new
                {
                    communicationUserId = responseValue.User.Id
                };

                var clientResponse = new
                {
                    user = jsonFormattedUser,
                    token = responseValue.AccessToken.Token,
                    expiresOn = responseValue.AccessToken.ExpiresOn
                };

                return this.Ok(clientResponse);
            }
            catch (RequestFailedException ex)
            {
                Console.WriteLine($"Error occured while Generating Token: {ex}");
                return this.Ok(this.Json(ex));
            }
        }

        /// <summary>
        /// Generate token for the User
        /// </summary>
        /// <returns></returns>
        [Route("/refreshToken/{identity}")]
        [HttpGet]
        public async Task<IActionResult> GetAsync(string identity)
        {
            try
            {
                CommunicationUserIdentifier identifier = new CommunicationUserIdentifier(identity);
                Response<AccessToken> response = await _client.GetTokenAsync(identifier, scopes: new[] { CommunicationTokenScope.Chat });

                var responseValue = response.Value;
                var clientResponse = new
                {
                    token = responseValue.Token,
                    expiresOn = responseValue.ExpiresOn
                };

                return this.Ok(clientResponse);
            }
            catch (RequestFailedException ex)
            {
                Console.WriteLine($"Error occured while Generating Token: {ex}");
                return this.Ok(this.Json(ex));
            }
        }
    }
}

 

Frontend:

Install the following packages in angular:

npm install @azure/communication-common --save

npm install @azure/communication-identity --save

npm install @azure/communication-signaling --save

npm install @azure/communication-chat --save

 

create new component azure-chat.component.ts : 

import { Component, OnInit } from '@angular/core';
import { ChatClient, ChatThreadClient } from '@azure/communication-chat';
import { HttpClient } from '@angular/common/http';
import { AzureCommunicationTokenCredential } from '@azure/communication-common';

@Component({
  selector: 'app-azure-chat',
  templateUrl: './azure-chat.component.html',
  styleUrls: ['./azure-chat.component.css']
})
export class AzureChatComponent implements OnInit {

  endpointUrl = 'https://test.westus.communication.azure.com';
  communicationUserId: string = '';
  userAccessToken: string = '';
  chatClient: ChatClient | any = null;
  topicName: string = '';
  displayName: string = '';
  threadLink: string = '';
  chatThreadClient: ChatThreadClient | any = null;

  otherCommunicationUserId: string = '';
  otherDisplayName: string = '';

  constructor(public http: HttpClient) { }

  ngOnInit(): void {

    // https://localhost:44316/token  -----> For create an User and Generate Token
    // https://localhost:44316/refreshToken/{identity}  -----> For Generate Token

    this.http.get('https://localhost:44316/token').subscribe((data: any) => {
      this.userAccessToken = data.token;
      this.communicationUserId = data.user.communicationUserId;
      this.chatClient = new ChatClient(this.endpointUrl, new AzureCommunicationTokenCredential(this.userAccessToken));
    });
  }

  async createChatThread() {
    let createChatThreadRequest = {
      topic: this.topicName
    };
    let createChatThreadOptions = {
      participants: [
        {
          id: { communicationUserId: this.communicationUserId },
          displayName: this.displayName
        }
      ]
    };
    let createChatThreadResult = await this.chatClient.createChatThread(
      createChatThreadRequest,
      createChatThreadOptions
    );
    let threadId = createChatThreadResult.chatThread.id;
    console.log(`Thread created:${threadId}`);
    this.threadLink = 'http://localhost:4200/chat/' + threadId;

    this.chatThreadClient = this.chatClient.getChatThreadClient(threadId);
  }

  async AddUserParticipantToTheChatThread() {
    let addParticipantsRequest =
    {
      participants: [
        {
          id: { communicationUserId: this.otherCommunicationUserId },
          displayName: this.otherDisplayName
        }
      ]
    };

    let addUser = await this.chatThreadClient.addParticipants(addParticipantsRequest);
    console.log(addUser);
  }

  async listAllChatThreads() {
    let threads = this.chatClient.listChatThreads();
    for await (const thread of threads) {
      console.log(thread);
    }
  }

}

 

azure-chat.component.html :

<label for="">Add Thread</label>
<br><br>

<input type="text" placeholder="Topic Name" [(ngModel)]="topicName" name="topicName">
<input type="text" placeholder="Your Display Name" [(ngModel)]="displayName" name="displayName">
<button (click)="createChatThread()">Submit</button>

<br><br>
<a *ngIf="threadLink" href="{{threadLink}}">{{threadLink}}</a>


<br><br><br><br><br>

<div *ngIf="threadLink">
    <label for="">Add user to thread</label>
    <br><br>
    
    <input type="text" placeholder="User id" [(ngModel)]="otherCommunicationUserId" name="otherCommunicationUserId">
    <input type="text" placeholder="Display Name" [(ngModel)]="otherDisplayName" name="otherDisplayName">
    <button (click)="AddUserParticipantToTheChatThread()">Submit</button>
</div>


 

create new component chat-thread.component.ts : 

import { Component, OnInit, ChangeDetectorRef  } from '@angular/core';
import { ChatClient, ChatThreadClient } from '@azure/communication-chat';
import { HttpClient } from '@angular/common/http';
import { AzureCommunicationTokenCredential } from '@azure/communication-common';
import { ActivatedRoute } from '@angular/router';


@Component({
  selector: 'app-chat-thread',
  templateUrl: './chat-thread.component.html',
  styleUrls: ['./chat-thread.component.css']
})
export class ChatThreadComponent implements OnInit {

  endpointUrl = 'https://test.westus.communication.azure.com';
  communicationUserId: any;
  userAccessToken: string = '';
  chatClient: ChatClient | any = null;
  topicName: string = '';
  displayName: string = '';
  currentThreadId: any;
  chatThreadClient: ChatThreadClient | any = null;

  messages: message[] = [];
  sendMsg: message = { message: '', sender: '' };

  constructor(public http: HttpClient,
    private changeDetection: ChangeDetectorRef,
    private route: ActivatedRoute) { }

  ngOnInit(): void {
    this.currentThreadId = this.route.snapshot.paramMap.get('threadid');
    this.communicationUserId = this.route.snapshot.paramMap.get('userid');

    this.http.get('https://localhost:44316/refreshToken/' + this.communicationUserId).subscribe((data: any) => {
      this.userAccessToken = data.token;
      //this.communicationUserId = data.user.communicationUserId;
      this.chatClient = new ChatClient(this.endpointUrl, new AzureCommunicationTokenCredential(this.userAccessToken));
      this.chatThreadClient = this.chatClient.getChatThreadClient(this.currentThreadId);
      this.setupHandlers();
    });
  }

  async setupHandlers() {
    this.getListMessages();

    await this.chatClient.startRealtimeNotifications();

    this.chatClient.on("chatMessageReceived", ((state: any) => {
      this.getListMessages();
    }).bind(this));
    
    this.chatClient.on("chatMessageEdited", ((state: any) => {
      this.getListMessages();
    }).bind(this));

    this.chatClient.on("chatMessageDeleted", ((state: any) => {
      this.getListMessages();
    }).bind(this));

    this.chatClient.on("typingIndicatorReceived", ((state: any) => {
      this.getListMessages();
    }).bind(this));

    this.chatClient.on("readReceiptReceived", ((state: any) => {
      this.getListMessages();
    }).bind(this));

    this.chatClient.on("chatThreadCreated", ((state: any) => {
      this.getListMessages();
    }).bind(this));

    this.chatClient.on("chatThreadDeleted", ((state: any) => {
      this.getListMessages();
    }).bind(this));

    this.chatClient.on("chatThreadPropertiesUpdated", ((state: any) => {
      this.getListMessages();
    }).bind(this));

    this.chatClient.on("participantsAdded", ((state: any) => {
      this.getListMessages();
    }).bind(this));

    this.chatClient.on("participantsRemoved", ((state: any) => {
      this.getListMessages();
    }).bind(this));
  }

  async getListMessages() {
    this.messages = [];
    const messages = this.chatThreadClient.listMessages();
    for await (const message of messages) {
      if (message.type == "text") {
        let msg: message = {
          sender: message.senderDisplayName,
          message: message.content.message
        };
        this.messages.push(msg);
      }
    }
    this.changeDetection.detectChanges();
  }

  async sendMessage() {
    let sendMessageRequest =
    {
      content: this.sendMsg.message
    };
    let sendMessageOptions =
    {
      senderDisplayName: this.sendMsg.sender,
      type: 'text'
    };
    const sendChatMessageResult = await this.chatThreadClient.sendMessage(sendMessageRequest, sendMessageOptions);
    const messageId = sendChatMessageResult.id;
    this.sendMsg = { message: '', sender: '' };
  }

  async ListUsersInChatThread() {
    const participants = this.chatThreadClient.listParticipants();
    for await (const participant of participants) {
      console.log(participant);
    }
  }

  async RemoveUserFromChatThread(userId: any) {
    await this.chatThreadClient.removeParticipant({ communicationUserId: userId });
    await this.ListUsersInChatThread();
  }

}

interface message {
  sender: string,
  message: string
}

 

chat-thread.component.html :

<div *ngFor="let message of messages;" class="row">
    <label for="">{{message.sender}}</label> : 
    <label for="">{{message.message}}</label>
</div>

<br><br><br><br>

<div class="col-md-12">
    <label for="">Send New Message</label>
    <br><br>
    
    <input type="text" placeholder="Sender" [(ngModel)]="sendMsg.sender" name="sender">
    <input type="text" placeholder="Message" [(ngModel)]="sendMsg.message" name="message">
    <button (click)="sendMessage()">Submit</button>
</div>

 

app-routing.module.ts : 

import { ChatThreadComponent } from './chat/chat-thread/chat-thread.component';
import { AzureChatComponent } from './chat/azure-chat/azure-chat.component';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  { path: '', component: AzureChatComponent },
  { path: 'chat/:threadid/:userid', component: ChatThreadComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

 

app.module.ts :

import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AzureChatComponent } from './chat/azure-chat/azure-chat.component';
import { ChatThreadComponent } from './chat/chat-thread/chat-thread.component';

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

 

Now you have to run Angular and API both projects and Assume the following ports:

  1. Angular : http://localhost:4200/
  2. Asp.Net API : https://localhost:44316/

 

Outputs :

  • Create user :

create azure user

 

  • Create Chat thread and Add Users :

create chat thread add users

 

  • Redirect to a Particular chat thread and Communicate with participants :

join azure chat thread

 

You can gift me a coffee from this link if I saved your valuable time 🙂

1 Comment

  1. Khanh Tran

    This example getting all the message, how can I make it lazy load with paging?

    0
    0
    Reply

Submit a Comment

Your email address will not be published. Required fields are marked *

Subscribe

Select Categories