DEV Community

Cover image for Build an Angular Video Chat App
Amos Gyamfi
Amos Gyamfi

Posted on • Originally published at getstream.io

Build an Angular Video Chat App

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.

This article guides you in building an Angular chat app with integrated video conferencing support. You can customize the final project to prioritize video conferencing with chat as a secondary feature.

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

A preview of the chat and video calling app

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

Chat UI with emojis

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 project in VS Code

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:

import { 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' },
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

<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>
Enter fullscreen mode Exit fullscreen mode

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.

@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%;
    }
}
Enter fullscreen mode Exit fullscreen mode

Test the Chat Functionality

Chat UI with action menu

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.

Server status image

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

Chat UI with video calling button

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.

GitHub Codespaces UI

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.

  • calling.service.spec.ts
  • calling.service.ts

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.


import { 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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

import { 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();
  });
});
Enter fullscreen mode Exit fullscreen mode

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.

Image showing project files

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.

Image showing participant files

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.

Add a Start Call Button to the Chat UI

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.

: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%);
}
Enter fullscreen mode Exit fullscreen mode

The modified content of style.css is shown below.

@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%;
  }
}
Enter fullscreen mode Exit fullscreen mode

Locate app.component.css, an empty CSS file that comes with Angular project generation. Fill out its content with the following style snippet.

.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;
}
Enter fullscreen mode Exit fullscreen mode

Next, let's add the call service to the HTML structure of the chat UI.

</ng-container>
  <ng-container *ngIf="callingService.callId()">
    <app-call [call]="callingService.call()!"></app-call>
</ng-container>
Enter fullscreen mode Exit fullscreen mode

You must add the code snippet above to the app.component.html file in the app directory.

<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>
Enter fullscreen mode Exit fullscreen mode

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.

import { 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);
  }
}
Enter fullscreen mode Exit fullscreen mode

Test the Integrated Chat and Video Calling Features

A preview of the 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.

Top comments (0)