AI Integration Quick Reference
Field Value Package @cometchat/chat-uikit-angularKey class CometChatTextFormatter (abstract base class)Required setup CometChatUIKit.init(uiKitSettings) then CometChatUIKit.login("UID")Purpose Detect #word patterns and render them as styled, highlighted hashtags Surfaces Message composer, message bubbles (text bubble), conversation last message, edit view Related Custom Text Formatter · Mentions Formatter · All Guides
This guide walks through building a hashtag formatter that detects #word patterns and renders them as highlighted, styled tokens. The formatter works across all text surfaces: the message composer (live as you type), message bubbles, conversation subtitle (last message preview), and the message edit view.
How It Works
User types #angular in the composer.
On space or enter, the formatter wraps the hashtag in a styled <span>.
When the message is sent, the raw text (#angular) is stored in the message body.
When the message is rendered in the message list, conversation list, or edit view, the formatter re-applies the highlight.
The formatter plugs into the textFormatters input on cometchat-message-list, cometchat-message-composer, and cometchat-conversations.
Prerequisites
Create a file hashtag-formatter.ts in your project:
import { CometChatTextFormatter } from "@cometchat/chat-uikit-angular" ;
export class HashtagFormatter extends CometChatTextFormatter {
readonly id = "hashtag-formatter" ;
override priority = 15 ;
private hashtags : string [] = [];
constructor () {
super ();
}
getRegex () : RegExp {
return / \B # ( \w {1,50} ) \b / g ;
}
format ( text : string ) : string {
if ( ! text ) {
this . originalText = "" ;
this . formattedText = "" ;
this . hashtags = [];
this . metadata = { hashtags: this . hashtags };
return "" ;
}
this . originalText = text ;
this . hashtags = [];
this . formattedText = text . replace ( this . getRegex (), ( match , tag ) => {
this . hashtags . push ( `# ${ tag } ` );
return `<span class="cometchat-hashtag" style="color: var(--cometchat-primary-color); font-weight: 600;"># ${ tag } </span>` ;
});
this . metadata = { hashtags: this . hashtags };
return this . formattedText ;
}
getHashtags () : string [] {
return [ ... this . hashtags ];
}
override reset () : void {
super . reset ();
this . hashtags = [];
}
}
See all 46 lines
Key points:
priority = 15 places it after the URL formatter (10) but before mentions (20), so URLs inside hashtags aren’t broken.
The regex \B#(\w{1,50})\b matches # preceded by a non-word boundary, followed by 1–50 word characters. This avoids matching # in URLs or hex colors.
The <span> uses var(--cometchat-primary-color) so it respects the active theme.
Step 2: Use in Message List and Composer
Pass the formatter to both the message list (for rendering) and the composer (for live preview and edit view):
import { Component , OnInit } from "@angular/core" ;
import { CometChat } from "@cometchat/chat-sdk-javascript" ;
import {
CometChatMessageListComponent ,
CometChatMessageComposerComponent ,
} from "@cometchat/chat-uikit-angular" ;
import { HashtagFormatter } from "./hashtag-formatter" ;
@ Component ({
selector: "app-chat" ,
standalone: true ,
imports: [ CometChatMessageListComponent , CometChatMessageComposerComponent ],
template: `
<cometchat-message-list
[user]="chatUser"
[textFormatters]="formatters">
</cometchat-message-list>
<cometchat-message-composer
[user]="chatUser"
[textFormatters]="formatters">
</cometchat-message-composer>
` ,
})
export class ChatComponent implements OnInit {
chatUser : CometChat . User | undefined ;
formatters = [ new HashtagFormatter ()];
ngOnInit () : void {
CometChat . getUser ( "uid" ). then (( user ) => {
this . chatUser = user ;
});
}
}
See all 33 lines
The textFormatters input accepts an array. The UIKit merges your custom formatters with the built-in ones (URL, mentions, emoji, markdown) and applies them in priority order.
Step 3: Use in Conversations (Last Message Preview)
To highlight hashtags in the conversation list’s last message subtitle, pass the same formatter to cometchat-conversations:
import { Component } from "@angular/core" ;
import { CometChatConversationsComponent } from "@cometchat/chat-uikit-angular" ;
import { HashtagFormatter } from "./hashtag-formatter" ;
@ Component ({
selector: "app-conversations" ,
standalone: true ,
imports: [ CometChatConversationsComponent ],
template: `
<cometchat-conversations
[textFormatters]="formatters"
(itemClick)="onConversationClick($event)">
</cometchat-conversations>
` ,
})
export class ConversationsComponent {
formatters = [ new HashtagFormatter ()];
onConversationClick ( conversation : any ) : void {
// handle conversation selection
}
}
See all 22 lines
This ensures #angular in the last message preview renders with the same highlight styling.
Step 4: Style the Hashtag
The inline style attribute handles the base color, but you can add a global CSS rule for more control:
.cometchat-hashtag {
color : var ( --cometchat-primary-color );
font : var ( --cometchat-font-body-medium );
cursor : pointer ;
}
.cometchat-hashtag:hover {
text-decoration : underline ;
}
Add this to your global styles.css or styles.scss.
Step 5: Handle Hashtag Clicks (Optional)
To make hashtags interactive (e.g., filter messages by tag), extend the formatter to emit click events:
import { CometChatTextFormatter } from "@cometchat/chat-uikit-angular" ;
import { Subject } from "rxjs" ;
export class HashtagFormatter extends CometChatTextFormatter {
readonly id = "hashtag-formatter" ;
override priority = 15 ;
/** Emits the clicked hashtag string */
hashtagClick$ = new Subject < string >();
private hashtags : string [] = [];
getRegex () : RegExp {
return / \B # ( \w {1,50} ) \b / g ;
}
format ( text : string ) : string {
if ( ! text ) {
this . originalText = "" ;
this . formattedText = "" ;
this . hashtags = [];
this . metadata = { hashtags: this . hashtags };
return "" ;
}
this . originalText = text ;
this . hashtags = [];
this . formattedText = text . replace ( this . getRegex (), ( match , tag ) => {
this . hashtags . push ( `# ${ tag } ` );
return `<span class="cometchat-hashtag" data-hashtag=" ${ tag } " style="color: var(--cometchat-primary-color); font-weight: 600; cursor: pointer;"># ${ tag } </span>` ;
});
this . metadata = { hashtags: this . hashtags };
return this . formattedText ;
}
getHashtags () : string [] {
return [ ... this . hashtags ];
}
override reset () : void {
super . reset ();
this . hashtags = [];
}
}
See all 46 lines
Then in your component, listen for clicks on .cometchat-hashtag elements:
import { Component , OnInit , OnDestroy , HostListener } from "@angular/core" ;
import { Subscription } from "rxjs" ;
import { HashtagFormatter } from "./hashtag-formatter" ;
@ Component ({ /* ... */ })
export class ChatComponent implements OnInit , OnDestroy {
private hashtagFormatter = new HashtagFormatter ();
private sub ?: Subscription ;
formatters = [ this . hashtagFormatter ];
ngOnInit () : void {
this . sub = this . hashtagFormatter . hashtagClick$ . subscribe (( tag ) => {
console . log ( "Hashtag clicked:" , tag );
// e.g., filter messages by tag, open search, etc.
});
}
@ HostListener ( "click" , [ "$event" ])
onDocumentClick ( event : MouseEvent ) : void {
const target = event . target as HTMLElement ;
if ( target . classList . contains ( "cometchat-hashtag" )) {
const tag = target . dataset [ "hashtag" ];
if ( tag ) {
this . hashtagFormatter . hashtagClick$ . next ( `# ${ tag } ` );
}
}
}
ngOnDestroy () : void {
this . sub ?. unsubscribe ();
}
}
See all 32 lines
Complete Example
A full two-panel layout with hashtag formatting across all surfaces:
import { Component , OnInit , OnDestroy , HostListener } from "@angular/core" ;
import { CometChat } from "@cometchat/chat-sdk-javascript" ;
import { Subscription } from "rxjs" ;
import {
CometChatConversationsComponent ,
CometChatMessageListComponent ,
CometChatMessageComposerComponent ,
CometChatMessageHeaderComponent ,
} from "@cometchat/chat-uikit-angular" ;
import { HashtagFormatter } from "./hashtag-formatter" ;
@ Component ({
selector: "app-chat-layout" ,
standalone: true ,
imports: [
CometChatConversationsComponent ,
CometChatMessageListComponent ,
CometChatMessageComposerComponent ,
CometChatMessageHeaderComponent ,
],
template: `
<div class="chat-layout">
<aside class="sidebar">
<cometchat-conversations
[textFormatters]="formatters"
(itemClick)="onConversationClick($event)">
</cometchat-conversations>
</aside>
<main class="messages">
@if (chatUser || chatGroup) {
<cometchat-message-header
[user]="chatUser"
[group]="chatGroup">
</cometchat-message-header>
<cometchat-message-list
[user]="chatUser"
[group]="chatGroup"
[textFormatters]="formatters">
</cometchat-message-list>
<cometchat-message-composer
[user]="chatUser"
[group]="chatGroup"
[textFormatters]="formatters">
</cometchat-message-composer>
}
</main>
</div>
` ,
styles: [ `
.chat-layout { display: flex; height: 100vh; }
.sidebar { width: 360px; border-right: 1px solid var(--cometchat-border-color-light); }
.messages { flex: 1; display: flex; flex-direction: column; }
` ],
})
export class ChatLayoutComponent implements OnInit , OnDestroy {
chatUser : CometChat . User | undefined ;
chatGroup : CometChat . Group | undefined ;
private hashtagFormatter = new HashtagFormatter ();
private sub ?: Subscription ;
formatters = [ this . hashtagFormatter ];
ngOnInit () : void {
this . sub = this . hashtagFormatter . hashtagClick$ . subscribe (( tag ) => {
console . log ( "Hashtag clicked:" , tag );
});
}
onConversationClick ( conversation : any ) : void {
const conversationType = conversation ?. getConversationType ?.();
if ( conversationType === "user" ) {
this . chatUser = conversation . getConversationWith () as CometChat . User ;
this . chatGroup = undefined ;
} else if ( conversationType === "group" ) {
this . chatGroup = conversation . getConversationWith () as CometChat . Group ;
this . chatUser = undefined ;
}
}
@ HostListener ( "click" , [ "$event" ])
onDocumentClick ( event : MouseEvent ) : void {
const target = event . target as HTMLElement ;
if ( target . classList . contains ( "cometchat-hashtag" )) {
const tag = target . dataset [ "hashtag" ];
if ( tag ) {
this . hashtagFormatter . hashtagClick$ . next ( `# ${ tag } ` );
}
}
}
ngOnDestroy () : void {
this . sub ?. unsubscribe ();
}
}
See all 94 lines
Surface How Input Message composer Live formatting as you type; hashtags highlight on space/enter [textFormatters] on cometchat-message-composerMessage bubbles Formatted when rendering text messages in the list [textFormatters] on cometchat-message-listConversation last message Formatted in the subtitle of each conversation item [textFormatters] on cometchat-conversationsEdit view Same formatters apply when editing a message in the composer [textFormatters] on cometchat-message-composer
The raw message text stored on the server is always plain text (e.g., Check out #angular). Formatting is applied at render time by the formatter pipeline. This means hashtag highlighting works retroactively on older messages too.
Next Steps
Custom Text Formatter Learn the full formatter API and lifecycle.
Mentions Formatter Add @mentions with suggestion lists.
Message Composer Customize the message input component.
All Guides Browse all feature and formatter guides.