Learn how to quickly integrate rich Generative AI experiences directly into Stream Chat. Learn More ->

Angular Chat App Tutorial

Build a fully functioning chat user experience using Stream's Angular SDK library. By the end of this tutorial, you will have a chat app with support for rich messages, reactions, file uploads and more.

We are also going to show how easy it is to make customizations to the Angular Chat components that ship with this library and their styling.

example of angular chat sdk

Set Up Your Angular Environment

The easiest way to build a Stream Chat Angular application from this tutorial is to create a new project using Angular CLI. Angular CLI builds an Angular boilerplate application that you can run locally with just a few simple commands.

Before you start: Make sure you've installed Angular CLI globally.

The SDK supports Angular versions from 15-18, you can complete the tutorial using any version. Angular 17-18 code snippets use standalone components, other versions are using module-based components.

Create a new application with Angular CLI:

shell
1
ng new chat-example --routing false --style scss --ssr false

Note You can use other style language if you wish.

Install the necessary dependencies:

Angular 18
Angular 17
Angular 16
Angular 15
shell
1
npm install stream-chat-angular ngx-float-ui@beta

Create a WhatsApp or Facebook Messenger Style Chat App

Stream's Angular Chat messaging SDK component library includes everything you need to build a fully functioning chat experience, supporting rich messages, reactions, image uploads, and more. This library was designed to enable you to get an application up and running quickly and efficiently while supporting customization for complex use cases.

We’ve included a few Stream Angular Chat examples to show you what’s possible:

  • Basic chat components cxample
  • Customizable UI components example

Build a Basic Chat App with Stream’s Core Angular Components

In the sample below, you’ll find the following basic chat components of the Angular library:

  • ChannelList
  • Channel
  • ChannelHeader
  • MessageInput
  • MessageList
  • Thread

To get started:

  1. Import the modules

Replace the src/app/app.config.ts file with the following:

typescript
1
2
3
4
5
6
7
8
9
10
import { ApplicationConfig, importProvidersFrom } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; export const appConfig: ApplicationConfig = { providers: [ // For Angular 18, you can enable the new change detection mode: // provideZoneChangeDetection({ eventCoalescing: true }), importProvidersFrom(TranslateModule.forRoot()) ], };

Add other modules to your AppComponent:

typescript
1
2
3
4
5
6
7
8
9
10
11
12
import { Component } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { StreamAutocompleteTextareaModule, StreamChatModule } from 'stream-chat-angular'; @Component({ selector: 'app-root', standalone: true, imports: [TranslateModule, StreamAutocompleteTextareaModule, StreamChatModule], templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) export class AppComponent {}

This will import the TranslateModule and the StreamChatModule.

If you already use ngx-translate in your application, follow our translation guide to set up translation.

In an effort to keep our bundle size small in the long run our SDK uses an architecture where integrators can decide to opt-out of certain costly (in terms of bundle size) features, this decision happens at module import, in the above example we chose what type of textarea component we want to use (StreamAutocompleteTextareaModule). You can find more information about this topic in our opt-in architecture guide.

  1. Init the chat application

Replace the content of your app.component.ts with the following code:

typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import { Component, OnInit } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { ChatClientService, ChannelService, StreamI18nService, StreamAutocompleteTextareaModule, StreamChatModule, } from 'stream-chat-angular'; @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_API_KEY'; const userId = 'REPLACE_WITH_USER_ID'; const userToken = 'REPLACE_WITH_USER_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' }, }); } }

First, we connect a user to the chat client. Further information about connecting users is available in our platform documentation.

Next, we create a channel (if doesn't exist) to test with. You can read more about channel creation in our platform documentation.

Lastly, we provide a filter condition for loading channels. If you want to know more about filtering channels, our platform documentation got you covered.

We also set up the translation for the application. If you want to customize it, check out our translation guide.

  1. Create the chat UI

Replace the content of the app.component.html with the following code:

html
1
2
3
4
5
6
7
8
9
10
11
12
13
<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>
  1. Import CSS

Add the following code to your root stylesheet (styles.scss if you are using SCSS):

css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@import 'stream-chat-angular/src/assets/styles/scss/index.scss'; 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%; } }

Important note If you are using CSS stylesheets, you should add the following import instead:

css
1
@import 'stream-chat-angular/src/assets/styles/css/index.css';

The SDK lets you create your own responsive layout, the code above contains a minimal setup where the channel list is displayed next to the channel component.

If Angular can't find the stylesheet, you should include node_modules in your angular.json for the build.options section:

plaintext
1
2
3
"stylePreprocessorOptions": { "includePaths": ["./node_modules"] }
  1. Modify tsconfig.json

Add the following option to the compilerOptions in tsconfig.json file:

plaintext
1
"allowSyntheticDefaultImports": true

We need to enable synthetic default imports becuase the stream-chat library uses them.

  1. Start the application

Run the following command:

shell
1
npm start

⚠️ If you run into SCSS issues, expand the section below and follow the steps.

Once you have the app running, you’ll notice the following out-of-the-box features:

  • User online presence
  • Message status indicators (sending, received)
  • User role configuration
  • Message read indicators
  • Message reactions
  • URL previews (send a link to see it in action, for example, https://getstream.io/)
  • GIFs (type /giphy in the message input)
  • Threads
  • File uploads and previews
  • AI-powered spam and profanity moderation

Creating Your Own UI Components

If the built-in theming capabilities are not enough for you, you can create your own custom components.

In the below example, you’ll create custom ChannelPreview and Message components.

  1. Create the Message component

Run the following command:

shell
1
ng g c message --inline-style --inline-template --standalone
  1. Implement the Message component

Replace the content of the message.component.ts with the following code:

typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Component, Input } from '@angular/core'; import { StreamMessage } from 'stream-chat-angular'; @Component({ selector: 'app-message', standalone: true, imports: [], template: ` <div> <b>{{ message?.user?.name }}</b> {{ message?.text }} </div> `, styles: ['b {margin-right: 4px}'], }) export class MessageComponent { @Input() message: StreamMessage | undefined; constructor() {} }
  1. Use the custom message component

Provide the customMessageTemplate to the CustomTemplatesService:

In the HTML of your AppComponent:

html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<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> <ng-template #customMessageTemplate let-message="message"> <app-message [message]="message"></app-message> </ng-template>

In the component class of your AppComponent

typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import { AfterViewInit, Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { ChatClientService, ChannelService, StreamI18nService, StreamAutocompleteTextareaModule, StreamChatModule, CustomTemplatesService, MessageContext, } from 'stream-chat-angular'; import { MessageComponent } from './message/message.component'; @Component({ selector: 'app-root', standalone: true, templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], imports: [TranslateModule, StreamAutocompleteTextareaModule, StreamChatModule, MessageComponent], }) export class AppComponent implements OnInit, AfterViewInit { // Create a reference to your custom template @ViewChild('customMessageTemplate') messageTemplate!: TemplateRef<MessageContext>; constructor( private chatService: ChatClientService, private channelService: ChannelService, private streamI18nService: StreamI18nService, private customTemplatesService: CustomTemplatesService, // Import the CustomTemplatesService ) { const apiKey = 'REPLACE_WITH_API_KEY'; const userId = 'REPLACE_WITH_USER_ID'; const userToken = 'REPLACE_WITH_USER_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' }, }); } ngAfterViewInit(): void { // Register your template this.customTemplatesService.messageTemplate$.next(this.messageTemplate); } }
  1. Create the ChannelPreview component

Run the following command:

shell
1
ng g c channel-preview --inline-style --inline-template --standalone
  1. Implement the ChannelPreview component

Replace the content of the channel-preview.component.ts with the following code:

typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { Component, Input, OnChanges } from '@angular/core'; import { Channel } from 'stream-chat'; import { ChannelService, DefaultStreamChatGenerics } from 'stream-chat-angular'; @Component({ selector: 'app-channel-preview', standalone: true, imports: [], template: ` <div class="container" (click)="setAsActiveChannel()"> <div>{{ channel?.data?.name || 'Unnamed Channel' }}</div> <div class="preview">{{ messagePreview }}</div> </div> `, styles: ['.container {margin: 12px}', '.preview {font-size: 14px}'], }) export class ChannelPreviewComponent implements OnChanges { @Input() channel: Channel<DefaultStreamChatGenerics> | undefined; messagePreview: string | undefined; constructor(private channelService: ChannelService) {} ngOnChanges(): void { const messages = this?.channel?.state?.messages; if (!messages) { return; } this.messagePreview = messages[messages.length - 1].text?.slice(0, 30); } setAsActiveChannel() { void this.channelService.setAsActiveChannel(this.channel!); } }
  1. Use the custom channel preview component

Provide the customChannelPreviewtemplate to the CustomTemplatesService:

In the HTML of your AppComponent

html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<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> <ng-template #customMessageTemplate let-message="message"> <app-message [message]="message"></app-message> </ng-template> <ng-template #customChannelPreviewTemplate let-channel="channel"> <app-channel-preview [channel]="channel"></app-channel-preview> </ng-template>

In the component class of your AppComponent

typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import { AfterViewInit, Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { ChatClientService, ChannelService, StreamI18nService, StreamAutocompleteTextareaModule, StreamChatModule, CustomTemplatesService, MessageContext, ChannelPreviewContext, } from 'stream-chat-angular'; import { MessageComponent } from './message/message.component'; import { ChannelPreviewComponent } from './channel-preview/channel-preview.component'; @Component({ selector: 'app-root', standalone: true, templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], imports: [TranslateModule, StreamAutocompleteTextareaModule, StreamChatModule, MessageComponent, ChannelPreviewComponent], }) export class AppComponent implements OnInit, AfterViewInit { @ViewChild('customMessageTemplate') messageTemplate!: TemplateRef<MessageContext>; @ViewChild('customChannelPreviewTemplate') channelPreviewTemplate!: TemplateRef<ChannelPreviewContext>; constructor( private chatService: ChatClientService, private channelService: ChannelService, private streamI18nService: StreamI18nService, private customTemplatesService: CustomTemplatesService, ) { const apiKey = 'REPLACE_WITH_API_KEY'; const userId = 'REPLACE_WITH_USER_ID'; const userToken = 'REPLACE_WITH_USER_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' }, }); } ngAfterViewInit(): void { this.customTemplatesService.messageTemplate$.next(this.messageTemplate); this.customTemplatesService.channelPreviewTemplate$.next(this.channelPreviewTemplate); } }

References

To learn more about the Angular SDK, check our SDK documentation and guides:

Final Thoughts

In this chat app tutorial we built a fully functioning Angular messaging app with our Angular SDK component library. We also showed how easy it is to customize the behavior and the style of the Angular chat app components with minimal code changes.

Both the chat SDK for Angular and the API have plenty more features available to support more advanced use-cases such as push notifications, content moderation, rich messages and more. Please check out our Android tutorial too. If you want some inspiration for your app, download our free chat interface UI kit.

Give us feedback!

Did you find this tutorial helpful in getting you up and running with your project? Either good or bad, we're looking for your honest feedback so we can improve.

Start coding for free

No credit card required.
If you're interested in a custom plan or have any questions, please contact us.