Popular chat messaging apps like WhatsApp and Telegram offer real-time video calls, while video conferencing apps like Zoom and Google Meet provide group chat during meetings. Chat apps with video calling and video conferencing apps with chat support focus on two similar communication needs.
Prerequisites
The two main features of the sample project in this tutorial are chat messaging and video calling. For messaging functionality, we will use the Stream Chat Angular SDK. We will also power video calling support with the above SDK's companion JavaScript Video SDK. By integrating these two SDKs as in-apps, developers can build seamless communication experiences, allowing people to send and receive chat messages, livestream, video call, join, and leave live audio room conversations in their apps.
You can recreate this tutorial’s project with your favorite IDE, preferably VS Code.
Run the Source Code
To test the sample project, you can clone and have a local version or create a GitHub Codespace for the app in this repo.
Start With the Chat Feature
In this section, you will build the Angular chat UI with a look and feel similar to the image above. The leading chat features include instant messaging, media attachments, emojis, threads and replies, and message interactions. Let's initiate a new Angular project and get started with the following steps.
Create a New Angular Project
Creating a new Angular project requires the installation of Node.js. Open Terminal or your favorite command line tool and check the version of Node on your machine with the following:
node -v
If you don't have Node installed, click the link above to install the latest stable (LTS) version.
Execute the following command to install the Angular CLI globally on your machine.
npm install -g @angular/cli
Note: On macOS, you may be required to prepend sudo
to the above command.
sudo npm install -g @angular/cli
With the Angular CLI installed, you can now create a new project with:
ng new video-chat-angular --routing false --style css --ssr false
where video-chat-angular
is the project name. For styling, we use CSS and also turn off routing. You can use whatever name you want for the project.
Open the app in your IDE and install the necessary dependencies as follows.
The example above shows the app opened in VS Code. Open the integrated terminal in VS Code and install Stream Chat Angular as a dependency with the following.
npm install stream-chat-angular stream-chat @ngx-translate/core
Stream-chat-angular
: Stream Chat Angular consists of reusable Angular chat components.stream-chat
: The core Angular SDK without UI components. You can use this to build a completely custom chat messaging experience.ngx-translate/core
: An internationalization (i18n) library for Angular projects.
Configure the Angular Chat SDK
Before we setup the Angular SDK, we should add the required imports in the project's src/app/app.config.ts
and src/app/app.component.ts
Open the file src/app/app.config.ts
and import the TranslateModule
in appConfig
with the following.
providers: [importProvidersFrom(TranslateModule.forRoot())],
Next, open src/app/app.component.ts
, and import the TranslateModule
, StreamAutocompleteTextareaModule
, StreamChatModule
in the @Component
decorator.
imports: [TranslateModule, StreamAutocompleteTextareaModule, StreamChatModule],
The modified content of this file should look like this:
1234567891011121314151617181920212223242526272829303132333435363738394041424344import { Component, OnInit } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { ChatClientService, ChannelService, StreamI18nService, StreamAutocompleteTextareaModule, StreamChatModule, } from 'stream-chat-angular'; // Video imports @Component({ selector: 'app-root', standalone: true, imports: [TranslateModule, StreamAutocompleteTextareaModule, StreamChatModule], templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) export class AppComponent implements OnInit { constructor( private chatService: ChatClientService, private channelService: ChannelService, private streamI18nService: StreamI18nService, ) { const apiKey = 'REPLACE_WITH_YOUR_STREAM_APIKEY'; const userId = 'REPLACE_WITH_YOUR_STREAM_USERID'; const userToken = 'REPLACE_WITH_TOKEN'; this.chatService.init(apiKey, userId, userToken); this.streamI18nService.setTranslation(); } async ngOnInit() { const channel = this.chatService.chatClient.channel('messaging', 'talking-about-angular', { // add as many custom fields as you'd like image: 'https://upload.wikimedia.org/wikipedia/commons/thumb/c/cf/Angular_full_color_logo.svg/2048px-Angular_full_color_logo.svg.png', name: 'Talking about Angular', }); await channel.create(); this.channelService.init({ type: 'messaging', id: { $eq: 'talking-about-angular' }, }); } }
In the code above, a valid user should be connected to the SDK's backend infrastructure to access the chat functionality. We are using a hard-coded user credential from our Angular Chat Tutorial. To implement a chat functionality for a production app using this SDK, you should sign up for a Dashboard account. On your Stream's Dashboard, you will find your API key to generate a token with our User JWT Generator. After filling out the user credentials, we create a new channel and define a condition to populate it when the app runs.
In addition to the above configurations, the Stream Chat SDK uses TypeScript's concept Allow Synthetic Default Imports to write default imports in a more efficient way. Add the following to tsconfig.json
in the project's root folder to turn it on.
"allowSyntheticDefaultImports": true
Configure the Chat UI Structure and Style
In our generated Angular project, the UI structure is defined in src/app/app.component.html
, while its styles are declared in src/app/styles.css
. Replace the HTML content of src/app/app.component.html
with the following to load the SDK's chat components such as Channel, Message List, Message Input, and Thread when the app launches.
12345678910111213<div id="root"> <stream-channel-list></stream-channel-list> <stream-channel> <stream-channel-header></stream-channel-header> <stream-message-list></stream-message-list> <stream-notification-list></stream-notification-list> <stream-message-input></stream-message-input> <stream-thread name="thread"> <stream-message-list mode="thread"></stream-message-list> <stream-message-input mode="thread"></stream-message-input> </stream-thread> </stream-channel> </div>
Let's define the following CSS styles in src/app/styles.css
. The styles we specify here do not include that of the video calling integration. We will modify this file again in a later section to cover that.
123456789101112131415161718192021222324252627@import 'stream-chat-angular/src/assets/styles/v2/css/index.css'; html { height: 100%; } body { margin: 0; height: 100%; } #root { display: flex; height: 100%; stream-channel-list { width: 30%; } stream-channel { width: 100%; } stream-thread { width: 45%; } }
Test the Chat Functionality
To start the development server in VS Code to run and test the chat version, launch the integrated Terminal and run:
ng serve
or npm start
When the server runs successfully, you should see a screen similar to the one below.
Open the link to the localhost http://localhost:4200/ in your preferred browser and start testing the chat interface.
For the rest of this tutorial, let's implement the ability to make video calls by clicking a button at the top-right of the messages list in the chat window.
Integrate the Video Calling Functionality
To allow users to initiate video calls from the chat UI, we will use Stream Video's low-level JavaScript client, which developers can implement with any other SDK or platform.
You can test the app's video calling feature by creating a GitHub Codespace from the final project. A Codespace allows you to run and test the app in the browser without cloning and installing it from the GitHub repo.
Install and Configure the JavaScript Video SDK
To install the Video SDK, navigate to the project’s root folder in VS Code, open a new Terminal instance and run the following.
npm i @stream-io/video-client
Then start the development server with:
npm start
Create a Video Calling Service
Ensure you are still in the project's app
folder /video-chat-angular/src/app
. Then, use the Angular CLI command below to generate a calling service.
ng generate service calling
After running the above, you will find two additional generated files When you expand the app
directory to see its content.
Note: The links above do not contain the original generated content. They have been modified to provide our calling service.
Let's open calling.service.ts
and substitute what is in the file with the following.
123456789101112131415161718192021222324252627282930313233343536373839404142import { Injectable, computed, signal } from '@angular/core'; import { Call, StreamVideoClient, User } from '@stream-io/video-client'; @Injectable({ providedIn: 'root', }) export class CallingService { callId = signal<string | undefined>(undefined); call = computed<Call | undefined>(() => { const currentCallId = this.callId(); if (currentCallId !== undefined) { const call = this.client.call('default', currentCallId); call.join({ create: true }).then(async () => { call.camera.enable(); call.microphone.enable(); }); return call; } else { return undefined; } }); client: StreamVideoClient; constructor() { const apiKey = '5cs7r9gv7sny'; const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiZmxvcmFsLW1lYWRvdy01In0.uASF95J3UqfS8zMtza0opjhtGk7r7xW0SJACsHXF0Do'; const user: User = { id: 'floral-meadow-5' }; this.client = new StreamVideoClient({ apiKey, token, user }); } setCallId(callId: string | undefined) { if (callId === undefined) { this.call()?.leave(); } this.callId.set(callId); } }
The service we created with the sample code above is designed to manage video calls, including joining and creating calls with video and audio enabled and leaving calls. We use a reactive programming pattern Angular Signals to manage state changes reactively. Check out our YouTube tutorial to learn more about creating a video calling app with Angular Signals.
First, we import the JavaScript video SDK, create a video client instance, and initialize it with an API key and token. Check out our documentation's Client and Authentication section to learn more about user types and how to generate a user token for your projects.
Finally, we create and join a call if it has not been created using the call.join
method. The call.join
method also allows real-time audio and video transmission.
Next, open calling.service.spec.ts
and replace the original code with the sample code below.
12345678910111213141516import { TestBed } from '@angular/core/testing'; import { CallingService } from './calling.service'; describe('CallingService', () => { let service: CallingService; beforeEach(() => { TestBed.configureTestingModule({}); service = TestBed.inject(CallingService); }); it('should be created', () => { expect(service).toBeTruthy(); }); });
The sample code above performs a basic unit test configuration for the CallingService
. It checks to see if a calling service can be created or successfully initiated.
How to Manage Audio/Video Calls
In this section, we should define a component to manage audio and video call-related functions such as toggling the microphone and camera on and off, accepting and leaving calls, and identifying participants with session IDs. Navigate to the app's src/app
directory and create a new folder call.
Add the following files inside the call folder and fill out each of their content by copying them from the GitHub project’s links below.
The files above provide the required UI and the logic for managing participants’ states and controlling microphone and camera states during audio/video calls.
Display and Manage a Call Participant
In this section, we should define a component that sets up the necessary bindings for displaying a participant's video and playing audio in a video call. Let's create the following directory src/app/participant
.
Add the participant's files in the image above in the call directory and replace each piece of content by copying it from its link to the GitHub project.
- participant.component.css
- participant.component.html
- participant.component.spec.ts
- participant.component.ts
Add a Start Call Button to the Chat UI
So far, the header of the chat UI's messages list consists of an avatar and the chat channel's title. Let's modify the message list’s header to add a start call button. We will also implement additional styles for the start button.
Open style.css
located in the project's root folder and add this code snippet below the @import
.
1234567:root { --custom-blue: hsl(218, 100%, 50%); --custom-blue-hover: hsl(218, 100%, 70%); --custom-red: hsl(0, 80%, 60%); --custom-red-hover: hsl(0, 80%, 50%); }
The modified content of style.css
is shown below.
1234567891011121314151617181920212223242526272829303132333435@import "stream-chat-angular/src/assets/styles/v2/css/index.css"; :root { --custom-blue: hsl(218, 100%, 50%); --custom-blue-hover: hsl(218, 100%, 70%); --custom-red: hsl(0, 80%, 60%); --custom-red-hover: hsl(0, 80%, 50%); } html { height: 100%; } body { margin: 0; height: 100%; } #root { display: flex; height: 100%; stream-channel-list { width: 30%; } stream-channel { width: 100%; } stream-thread { width: 45%; } }
Locate app.component.css
, an empty CSS file that comes with Angular project generation. Fill out its content with the following style snippet.
123456789101112131415.header-container { display: flex; align-items: center; justify-content: space-between; padding: 0 1rem; } button { background: #005fff; color: white; padding: 0.5rem 1rem; border-radius: 0.25rem; border: none; cursor: pointer; }
Next, let's add the call service to the HTML structure of the chat UI.
1234</ng-container> <ng-container *ngIf="callingService.callId()"> <app-call [call]="callingService.call()!"></app-call> </ng-container>
You must add the code snippet above to the app.component.html
file in the app
directory.
123456789101112131415161718192021<div id="root"> <stream-channel-list></stream-channel-list> <ng-container *ngIf="!callingService.callId()"> <stream-channel> <div class="header-container"> <stream-channel-header></stream-channel-header> <button (click)="startCall()">Start call</button> </div> <stream-message-list></stream-message-list> <stream-notification-list></stream-notification-list> <stream-message-input></stream-message-input> <stream-thread name="thread"> <stream-message-list mode="thread"></stream-message-list> <stream-message-input mode="thread"></stream-message-input> </stream-thread> </stream-channel> </ng-container> <ng-container *ngIf="callingService.callId()"> <app-call [call]="callingService.call()!"></app-call> </ng-container> </div>
Finally, we should modify the app.component.ts
file that was added in one of the chat sections to import the CallingService
and CallComponent
and add a startCall
method. The modified sample code in app.component.ts
should look like below.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970import { Component, OnInit } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { ChatClientService, ChannelService, StreamI18nService, StreamAutocompleteTextareaModule, StreamChatModule, } from 'stream-chat-angular'; import { CallingService } from './calling.service'; import { CommonModule } from '@angular/common'; // Video imports import { CallComponent } from './call/call.component'; @Component({ selector: 'app-root', standalone: true, imports: [ TranslateModule, StreamAutocompleteTextareaModule, StreamChatModule, CommonModule, CallComponent, ], templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) export class AppComponent implements OnInit { callingService: CallingService; constructor( private chatService: ChatClientService, private channelService: ChannelService, private streamI18nService: StreamI18nService, callingService: CallingService ) { this.callingService = callingService; const apiKey = '5cs7r9gv7sny'; const userId = 'floral-meadow-5'; const userName = 'Floral Meadow'; const userToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiZmxvcmFsLW1lYWRvdy01In0.uASF95J3UqfS8zMtza0opjhtGk7r7xW0SJACsHXF0Do'; this.chatService.init(apiKey, userId, userToken); this.streamI18nService.setTranslation(); } async ngOnInit() { const channel = this.chatService.chatClient.channel( 'messaging', 'talking-about-angular', { // add as many custom fields as you'd like image: 'https://upload.wikimedia.org/wikipedia/commons/thumb/c/cf/Angular_full_color_logo.svg/2048px-Angular_full_color_logo.svg.png', name: 'Talking about Angular', } ); await channel.create(); this.channelService.init({ type: 'messaging', id: { $eq: 'talking-about-angular' }, }); } startCall() { const channelId = this.channelService.activeChannel?.id; this.callingService.setCallId(channelId); } }
Test the Integrated Chat and Video Calling Features
Start the development server again with:
npm start
The start call button you just implemented will appear at the top right of the message list's header. Clicking the button presents a live video of the local participant. On the live video or active call screen, there are buttons for performing standard call operations like switching the camera and microphone on and off and ending a call.
Conclusion
Congratulations! 👏 You have followed all the steps to build a real-time and fully functioning Angular video chat application. The app allows users to send and receive rich text chats, add media as attachments, and connect with others through face-to-face video calling. Explore the related links of this tutorial to learn more about the advanced features and capabilities of the Angular Chat and JavaScript Video SDKs.