You can take a look at the application we're going to build below:
Here are the main features of the application:
- User online presence
- Typing indicators
- Emoji support
- Message read indicators
- Threading and message replies
- Message reactions
- URL previews (send a YouTube link to see this in action)
- File and image uploads and previews
- Slash commands (try typing /giphy...)
- Autocomplete-enabled search on users
- AI-powered spam and profanity moderation
You can also checkout the demo application if you can't wait for the tutorial to end to try these.
Project Setup and Installation
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.
The SDK supports Angular (CLI) versions from 15-19, you can complete the tutorial using any version within that range.
This is how you can check your Angular CLI version:
1ng version
If you don't yet have Angular CLI installed globally, you can follow the official Angular CLI guide.
Create a new application with Angular CLI:
1ng new chat-example --routing false --style scss --ssr false
1ng new chat-example --routing false --style scss
Note You can use other style language if you wish, but the tutorial will showcase SCSS code, so you need to adjust your code.
Install the necessary dependencies:
12345# Some SDK depedencies doesn't yet have official support for Angular 19, so we need to add --force flag until then npm install stream-chat-angular stream-chat @ngx-translate/core@15 ngx-float-ui@18 --force # or yarn add stream-chat-angular stream-chat @ngx-translate/core@15 ngx-float-ui@18
1234npm install stream-chat-angular stream-chat @ngx-translate/core@15 ngx-float-ui@18 # or yarn add stream-chat-angular stream-chat @ngx-translate/core@15 ngx-float-ui@18
1234npm install stream-chat-angular stream-chat @ngx-translate/core@15 ngx-float-ui@17 # or yarn add stream-chat-angular stream-chat @ngx-translate/core@15 ngx-float-ui@17
1234npm install stream-chat-angular stream-chat @ngx-translate/core@15 ngx-float-ui@16 # or yarn add stream-chat-angular stream-chat @ngx-translate/core@15 ngx-float-ui@16
1234npm install stream-chat-angular stream-chat @ngx-translate/core@14 ngx-float-ui@15 # or yarn add stream-chat-angular stream-chat @ngx-translate/core@14 ngx-float-ui@15
Note Angular 17-19 code snippets use standalone components (as those are default in those versions), Angular 15-16 code snippets use module-base components. In case you're doing a tutorial in an existing project, you might need to adjust which code snippets you follow.
Import Angular Modules
Replace the src/app/app.config.ts
file with the following:
123456import { ApplicationConfig, importProvidersFrom, provideZoneChangeDetection } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; export const appConfig: ApplicationConfig = { providers: [provideZoneChangeDetection({ eventCoalescing: true }), importProvidersFrom(TranslateModule.forRoot())], };
Add other modules to your AppComponent
:
123456789101112import { 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 {}
Replace the src/app/app.config.ts
file with the following:
123456import { ApplicationConfig, importProvidersFrom } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; export const appConfig: ApplicationConfig = { providers: [importProvidersFrom(TranslateModule.forRoot())], };
Add other modules to your AppComponent
:
123456789101112import { 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 {}
Replace the content of your AppModule
with the following code:
1234567891011121314import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { TranslateModule } from '@ngx-translate/core'; import { AppComponent } from './app.component'; import { StreamChatModule, StreamAutocompleteTextareaModule } from 'stream-chat-angular'; @NgModule({ declarations: [AppComponent], imports: [BrowserModule, TranslateModule.forRoot(), StreamAutocompleteTextareaModule, StreamChatModule], providers: [], bootstrap: [AppComponent], }) export class AppModule {}
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.
Stream Client Setup
To make the tutorial as easy as possible each of our code samples in this tutorial comes with generated credentials for you to pick-up and use, these credentials consist of:
apiKey
- an API key that is used to identify your Stream application by our serversuserId
anduserToken
- authorization information of the current chat useruserName
- optional, used as a display name of the current chat user
Now if you already have and wish to use your own Stream application, you can certainly do so - feel free to use our token (JWT) generator utility to easily generate authorization token for your user, or use your own token generator.
Replace the content of your app.component.ts
with the following code:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152import { Component, OnInit } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { User } from 'stream-chat'; 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'; const userName = 'REPLACE_WITH_USER_NAME'; const user: User = { id: userId, name: userName, image: `https://getstream.io/random_png/?name=${userName}`, }; this.chatService.init(apiKey, user, 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' }, }); } }
12345678910111213141516171819202122232425262728293031323334353637383940414243import { Component, OnInit } from '@angular/core'; import { User } from 'stream-chat'; import { ChatClientService, ChannelService, StreamI18nService } from 'stream-chat-angular'; @Component({ selector: 'app-root', 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'; const userName = 'REPLACE_WITH_USER_NAME'; const user: User = { id: userId, name: userName, image: `https://getstream.io/random_png/?name=${userName}`, }; this.chatService.init(apiKey, user, 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 yet 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.
Core components
After intializing the client, let's create a simple chat UI with a channel list and channel components.
Replace the content of the app.component.html
with the following code:
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>
Add the following code to your root stylesheet (styles.scss
if you are using SCSS):
123456789101112131415161718192021222324252627@use '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: 70%; } stream-thread { width: 45%; } }
Important note If you are using CSS stylesheets, you should add the following import instead:
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 you're using Angular 16 or 15, add the following option to the compilerOptions
in tsconfig.json
file:
1"allowSyntheticDefaultImports": true
That's it, we can now run the application:
1npm start
⚠️ If you run into SCSS issues, expand the section below and follow the steps.
If you run into SCSS issues related to icons, you should extend the stylesheet import like this:
1234@use "stream-chat-angular/src/assets/styles/scss/variables" with ( $assetsPath: "../node_modules/stream-chat-angular/src/assets/assets" ); @use 'stream-chat-angular/src/assets/styles/scss/index.scss';
You can read more about the underlying issue on Angular's GitHub.
Theming
The SDK supports light and dark themes out-of-the-box, this is how you can update your app.component.ts
to toggle between them:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657import { Component, OnInit } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { User } from 'stream-chat'; import { ChatClientService, ChannelService, StreamI18nService, StreamAutocompleteTextareaModule, StreamChatModule, ThemeService, } 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, private themeService: ThemeService, ) { // or light this.themeService.theme$.next('dark'); const apiKey = 'REPLACE_WITH_API_KEY'; const userId = 'REPLACE_WITH_USER_ID'; const userToken = 'REPLACE_WITH_USER_TOKEN'; const userName = 'REPLACE_WITH_USER_NAME'; const user: User = { id: userId, name: userName, image: `https://getstream.io/random_png/?name=${userName}`, }; this.chatService.init(apiKey, user, 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' }, }); } }
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647import { Component, OnInit } from '@angular/core'; import { User } from 'stream-chat'; import { ChatClientService, ChannelService, StreamI18nService, ThemeService } from 'stream-chat-angular'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) export class AppComponent implements OnInit { constructor( private chatService: ChatClientService, private channelService: ChannelService, private streamI18nService: StreamI18nService, private themeService: ThemeService, ) { // or light this.themeService.theme$.next('dark'); const apiKey = 'REPLACE_WITH_API_KEY'; const userId = 'REPLACE_WITH_USER_ID'; const userToken = 'REPLACE_WITH_USER_TOKEN'; const userName = 'REPLACE_WITH_USER_NAME'; const user: User = { id: userId, name: userName, image: `https://getstream.io/random_png/?name=${userName}`, }; this.chatService.init(apiKey, user, 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' }, }); } }
Theming is done using CSS variables, you can override these CSS variable like this:
12345678910111213141516171819202122232425262728293031@use 'stream-chat-angular/src/assets/styles/scss/index.scss'; .str-chat__theme-dark { --str-chat__primary-color: #009688; } html { height: 100%; } body { margin: 0; height: 100%; } #root { display: flex; height: 100%; stream-channel-list { width: 30%; } stream-channel { width: 70%; } stream-thread { width: 45%; } }
It's also possible to create your own theme, for a detailed instruction, follow our theming guide
Custom components
Sometimes theming is not enough to customize chat components.
Luckily, the SDK supports a number of slots to override specific parts of the built-in chat UI. In this tutorial we'll show how to create your own message and channel preview components.
Custom message component
In the below example, you’ll create a custom Message
component.
- Create the
Message
component
Run the following command:
1ng g c message --inline-style --inline-template --standalone
1ng g c message --inline-style --inline-template
- Implement the
Message
component
Replace the content of the message.component.ts
with the following code:
123456789101112131415161718import { 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: ['div {height: 50px} b {margin-right: 4px}'], }) export class MessageComponent { @Input() message: StreamMessage | undefined; constructor() {} }
12345678910111213141516import { Component, Input } from '@angular/core'; import { StreamMessage } from 'stream-chat-angular'; @Component({ selector: 'app-message', template: ` <div> <b>{{ message?.user?.name }}</b> {{ message?.text }} </div> `, styles: ['div {height: 50px} b {margin-right: 4px}'], }) export class MessageComponent { @Input() message: StreamMessage | undefined; constructor() {} }
- Use the custom message component
Provide the customMessageTemplate
to the CustomTemplatesService
:
In the HTML of your AppComponent
:
1234567891011121314151617<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
register the custom template:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768import { AfterViewInit, Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { User } from 'stream-chat'; import { TranslateModule } from '@ngx-translate/core'; import { ChatClientService, ChannelService, StreamI18nService, StreamAutocompleteTextareaModule, StreamChatModule, CustomTemplatesService, MessageContext, ThemeService, } 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 themeService: ThemeService, private customTemplatesService: CustomTemplatesService, // Import the CustomTemplatesService ) { this.themeService.theme$.next('dark'); const apiKey = 'REPLACE_WITH_API_KEY'; const userId = 'REPLACE_WITH_USER_ID'; const userToken = 'REPLACE_WITH_USER_TOKEN'; const userName = 'REPLACE_WITH_USER_NAME'; const user: User = { id: userId, name: userName, image: `https://getstream.io/random_png/?name=${userName}`, }; this.chatService.init(apiKey, user, 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); } }
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162import { AfterViewInit, Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { User } from 'stream-chat'; import { ChatClientService, ChannelService, StreamI18nService, MessageContext, CustomTemplatesService, ThemeService, } from 'stream-chat-angular'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) 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 themeService: ThemeService, private customTemplatesService: CustomTemplatesService, // Import the CustomTemplatesService ) { this.themeService.theme$.next('dark'); const apiKey = 'REPLACE_WITH_API_KEY'; const userId = 'REPLACE_WITH_USER_ID'; const userToken = 'REPLACE_WITH_USER_TOKEN'; const userName = 'REPLACE_WITH_USER_NAME'; const user: User = { id: userId, name: userName, image: `https://getstream.io/random_png/?name=${userName}`, }; this.chatService.init(apiKey, user, 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); } }
Custom channel preview component
- Create the
ChannelPreview
component
Run the following command:
1ng g c channel-preview --inline-style --inline-template --standalone
1ng g c channel-preview --inline-style --inline-template
- Implement the
ChannelPreview
component
Replace the content of the channel-preview.component.ts
with the following code:
12345678910111213141516171819202122232425262728293031323334import { 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!); } }
1234567891011121314151617181920212223242526272829303132import { Component, Input, OnChanges } from '@angular/core'; import { Channel } from 'stream-chat'; import { ChannelService, DefaultStreamChatGenerics } from 'stream-chat-angular'; @Component({ selector: 'app-channel-preview', 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!); } }
- Use the custom channel preview component
Provide the customChannelPreviewtemplate
to the CustomTemplatesService
:
In the HTML of your AppComponent
123456789101112131415161718192021<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
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970import { AfterViewInit, Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { User } from 'stream-chat'; import { ChatClientService, ChannelService, StreamI18nService, StreamAutocompleteTextareaModule, StreamChatModule, CustomTemplatesService, MessageContext, ChannelPreviewContext, ThemeService, } 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 themeService: ThemeService, private customTemplatesService: CustomTemplatesService, ) { this.themeService.theme$.next('dark'); const apiKey = 'REPLACE_WITH_API_KEY'; const userId = 'REPLACE_WITH_USER_ID'; const userToken = 'REPLACE_WITH_USER_TOKEN'; const userName = 'REPLACE_WITH_USER_NAME'; const user: User = { id: userId, name: userName, image: `https://getstream.io/random_png/?name=${userName}`, }; this.chatService.init(apiKey, user, 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); } }
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263import { AfterViewInit, Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { User } from 'stream-chat'; import { ChatClientService, ChannelService, StreamI18nService, MessageContext, CustomTemplatesService, ChannelPreviewContext, ThemeService, } from 'stream-chat-angular'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) 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 themeService: ThemeService, private customTemplatesService: CustomTemplatesService, ) { this.themeService.theme$.next('dark'); const apiKey = 'REPLACE_WITH_API_KEY'; const userId = 'REPLACE_WITH_USER_ID'; const userToken = 'REPLACE_WITH_USER_TOKEN'; const userName = 'REPLACE_WITH_USER_NAME'; const user: User = { id: userId, name: userName, image: `https://getstream.io/random_png/?name=${userName}`, }; this.chatService.init(apiKey, user, 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); } }
Custom attachments
Custom messages & attachments allow you to bring more rich content into the chat experience. Marketplace apps use it to display offers, hospitality apps often show your reservation and apps like Strava use it to show cycling routes. Other use cases include custom video messages, polls, live locations etc.
You can build any type of custom message or attachment with Stream. If you're intrested in building custom attachments, you can check out the custom attachment guide.
Emoji picker
No chat experience is complete without emojis - we've made it easy for you to extend your message input component with an emoji picker, you can find the detailed instruction in the Emoji picker guide.
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.