Skip to main content
The FormatterConfigService is a centralized Angular service for managing default text formatters across the CometChat UIKit. It provides a single source of truth for formatter configuration, allowing you to set formatters once and have them automatically applied across all text-displaying components.

Overview

The service manages text formatters that transform raw text into formatted HTML with support for:
  • Mentions (@user) with self-mention detection
  • URLs with automatic link creation
  • Custom formatters (hashtags, emails, etc.)
  • Context-aware formatting (logged-in user, message alignment)
  • Formatter priority ordering
  • Performance optimization through instance reuse

Installation

The service is provided at root level and automatically available throughout your application:
import { inject } from '@angular/core';
import { FormatterConfigService } from '@cometchat/chat-uikit-angular';

export class MyComponent {
  private formatterConfig = inject(FormatterConfigService);
}

Default Behavior

When no custom formatters are configured, the service provides two built-in formatters:
  1. CometChatMentionsFormatter (priority 20)
    • Detects mentions in format <@uid:{uid}> or <@all:{label}>
    • Converts to HTML spans with CSS classes
    • Applies self-mention detection
    • Adds direction classes for message alignment
  2. CometChatUrlFormatter (priority 100)
    • Detects URLs in text
    • Converts to clickable links
    • Adds security attributes (target="_blank", rel="noopener noreferrer")
These formatters are singleton instances, reused across all components for optimal performance.

Core Methods

getDefaultFormatters(): CometChatTextFormatter[]

Gets the configured default formatters. Returns: Array of text formatters in priority order Priority:
  1. Custom formatters (if set via setDefaultFormatters)
  2. Built-in defaults + additional formatters (if added via addFormatters)
  3. Built-in defaults only (mentions + URLs)
Example:
export class TextBubbleComponent implements OnInit {
  private formatterConfig = inject(FormatterConfigService);
  private formatters: CometChatTextFormatter[] = [];

  ngOnInit() {
    // Get default formatters
    this.formatters = this.formatterConfig.getDefaultFormatters();
  }
}

setDefaultFormatters(formatters: CometChatTextFormatter[]): void

Replaces the built-in default formatters with custom formatters. Parameters:
  • formatters: Array of custom formatters to use as defaults
Use Case: When you want complete control over which formatters are used globally. Example:
// In app initialization (main.ts or app.component.ts)
export class AppComponent implements OnInit {
  private formatterConfig = inject(FormatterConfigService);

  ngOnInit() {
    // Replace defaults with custom formatters
    const customFormatters = [
      new MyCustomMentionsFormatter(),
      new MyCustomUrlFormatter(),
      new HashtagFormatter(),
      new EmailFormatter()
    ];
    
    this.formatterConfig.setDefaultFormatters(customFormatters);
  }
}
Note: Once custom formatters are set, addFormatters() has no effect. To add to custom formatters, include them in the array passed to setDefaultFormatters().

addFormatters(formatters: CometChatTextFormatter[]): void

Adds additional formatters to the built-in defaults without replacing them. Parameters:
  • formatters: Array of formatters to add to the default set
Use Case: When you want to keep the built-in formatters (mentions + URLs) and add custom ones. Example:
export class AppComponent implements OnInit {
  private formatterConfig = inject(FormatterConfigService);

  ngOnInit() {
    // Add custom formatters to defaults
    const additionalFormatters = [
      new HashtagFormatter(),
      new EmailFormatter(),
      new PhoneNumberFormatter()
    ];
    
    this.formatterConfig.addFormatters(additionalFormatters);
    
    // Now getDefaultFormatters() returns:
    // [mentions, URLs, hashtag, email, phone]
  }
}
Note: If custom formatters have been set via setDefaultFormatters(), this method has no effect.

resetToDefaults(): void

Resets to the built-in default formatters, clearing any custom or additional formatters. Use Case: When you want to restore the original formatter configuration. Example:
export class SettingsComponent {
  private formatterConfig = inject(FormatterConfigService);

  resetFormatters() {
    // Clear all customizations
    this.formatterConfig.resetToDefaults();
    
    // Now getDefaultFormatters() returns:
    // [mentions, URLs]
  }
}

getFormattersWithContext(loggedInUser?, alignment?): CometChatTextFormatter[]

Gets formatters configured with context for self-mention detection and direction CSS classes. Parameters:
  • loggedInUser (optional): The logged-in user for self-mention detection
  • alignment (optional): Message bubble alignment for direction CSS classes
Returns: Array of formatters configured with the provided context Context Configuration: When loggedInUser is provided:
  • Mentions formatter applies cometchat-mentions-you for self-mentions
  • Mentions formatter applies cometchat-mentions-other for other-user mentions
When alignment is provided:
  • MessageBubbleAlignment.left → adds cometchat-mentions-incoming class
  • MessageBubbleAlignment.right → adds cometchat-mentions-outgoing class
  • undefined → no direction classes (for composer, previews, conversation list)
Example 1: Message List (with alignment)
export class MessageListComponent implements OnInit {
  private formatterConfig = inject(FormatterConfigService);
  private formatters: CometChatTextFormatter[] = [];

  ngOnInit() {
    const loggedInUser = CometChat.getLoggedInUser();
    
    // Get formatters with context for each message
    this.messages.forEach(message => {
      const alignment = this.getMessageAlignment(message);
      const formatters = this.formatterConfig.getFormattersWithContext(
        loggedInUser,
        alignment
      );
      
      // Pass to text bubble component
      message.formatters = formatters;
    });
  }
  
  private getMessageAlignment(message: CometChat.BaseMessage): MessageBubbleAlignment {
    const loggedInUser = CometChat.getLoggedInUser();
    return message.getSender().getUid() === loggedInUser?.getUid()
      ? MessageBubbleAlignment.right
      : MessageBubbleAlignment.left;
  }
}
Example 2: Message Composer (without alignment)
export class MessageComposerComponent implements OnInit {
  private formatterConfig = inject(FormatterConfigService);
  private formatters: CometChatTextFormatter[] = [];

  ngOnInit() {
    const loggedInUser = CometChat.getLoggedInUser();
    
    // Get formatters without alignment for composer
    // No direction CSS classes will be applied
    this.formatters = this.formatterConfig.getFormattersWithContext(
      loggedInUser,
      undefined
    );
  }
}
Example 3: Conversation List (without alignment)
export class ConversationItemComponent implements OnInit {
  private formatterConfig = inject(FormatterConfigService);
  private formatters: CometChatTextFormatter[] = [];

  ngOnInit() {
    const loggedInUser = CometChat.getLoggedInUser();
    
    // Get formatters without alignment for conversation subtitle
    // No direction CSS classes will be applied
    this.formatters = this.formatterConfig.getFormattersWithContext(
      loggedInUser,
      undefined
    );
  }
}

Usage Patterns

The simplest approach - let the service provide the default formatters.
export class TextBubbleComponent implements OnInit {
  private formatterConfig = inject(FormatterConfigService);
  private formatters: CometChatTextFormatter[] = [];

  ngOnInit() {
    // Get default formatters (mentions + URLs)
    this.formatters = this.formatterConfig.getDefaultFormatters();
  }
}
When to use: Most components should use this pattern.

Pattern 2: Set Custom Formatters Globally

Replace the built-in formatters with your own custom set.
// In app initialization (main.ts or app.component.ts)
export class AppComponent implements OnInit {
  private formatterConfig = inject(FormatterConfigService);

  ngOnInit() {
    // Define custom formatters
    const customFormatters = [
      new MyCustomMentionsFormatter(),
      new MyCustomUrlFormatter(),
      new HashtagFormatter()
    ];
    
    // Set as defaults
    this.formatterConfig.setDefaultFormatters(customFormatters);
  }
}
When to use: When you need complete control over formatter behavior globally.

Pattern 3: Extend Default Formatters

Keep the built-in formatters and add custom ones.
export class AppComponent implements OnInit {
  private formatterConfig = inject(FormatterConfigService);

  ngOnInit() {
    // Add custom formatters to defaults
    const additionalFormatters = [
      new HashtagFormatter(),
      new EmailFormatter()
    ];
    
    this.formatterConfig.addFormatters(additionalFormatters);
    
    // All components now get: [mentions, URLs, hashtag, email]
  }
}
When to use: When you want to add functionality without losing the built-in formatters.

Pattern 4: Context-Aware Formatting

Configure formatters with logged-in user and message alignment.
export class MessageListComponent implements OnInit {
  private formatterConfig = inject(FormatterConfigService);

  ngOnInit() {
    const loggedInUser = CometChat.getLoggedInUser();
    
    this.messages.forEach(message => {
      const alignment = this.getMessageAlignment(message);
      
      // Get formatters with context
      const formatters = this.formatterConfig.getFormattersWithContext(
        loggedInUser,
        alignment
      );
      
      // Use formatters for this message
      this.formatMessage(message, formatters);
    });
  }
}
When to use: In message lists where self-mention detection and direction classes are needed.

Pattern 5: Component-Specific Formatters

Override formatters for a specific component instance.
export class CustomTextBubbleComponent implements OnInit {
  @Input() textFormatters?: CometChatTextFormatter[];
  
  private formatterConfig = inject(FormatterConfigService);
  private formatters: CometChatTextFormatter[] = [];

  ngOnInit() {
    // Use input formatters if provided, otherwise use defaults
    this.formatters = this.textFormatters || 
                      this.formatterConfig.getDefaultFormatters();
  }
}
When to use: When a specific component needs different formatters than the global defaults.

Creating Custom Formatters

To create a custom formatter, extend CometChatTextFormatter:
import { CometChatTextFormatter } from '@cometchat/chat-uikit-angular';

export class HashtagFormatter extends CometChatTextFormatter {
  constructor() {
    super();
    this.priority = 50; // Execute after mentions (20) but before URLs (100)
  }

  getRegex(): RegExp {
    // Match hashtags: #word
    return /#(\w+)/g;
  }

  format(text: string, message?: CometChat.BaseMessage): string {
    return text.replace(this.getRegex(), (match, tag) => {
      return `<span class="cometchat-hashtag" data-tag="${tag}">#${tag}</span>`;
    });
  }

  shouldFormat(text: string, message?: CometChat.BaseMessage): boolean {
    // Only format if text contains hashtags
    return this.getRegex().test(text);
  }
}
Using the custom formatter:
// Add to defaults
this.formatterConfig.addFormatters([new HashtagFormatter()]);

// Or set as custom defaults
this.formatterConfig.setDefaultFormatters([
  new CometChatMentionsFormatter(),
  new HashtagFormatter(),
  new CometChatUrlFormatter()
]);

Formatter Priority

Formatters execute in order of their priority property (lower number = earlier execution):
FormatterPriorityExecution Order
Mentions20First
Emoji30Second
Custom (example)50Third
URLs100Last
Why priority matters:
// Text: "Check out @john's profile at https://example.com"

// Execution order:
// 1. Mentions formatter (priority 20) → formats @john
// 2. URL formatter (priority 100) → formats https://example.com

// Result: Both mentions and URLs are properly formatted
Setting priority in custom formatters:
export class EmailFormatter extends CometChatTextFormatter {
  constructor() {
    super();
    this.priority = 90; // Execute before URLs but after mentions
  }
}

Performance Optimization

The service optimizes performance through:

1. Singleton Instances

Formatters are created once and reused across all components:
// ✅ GOOD: Service reuses instances
const formatters1 = this.formatterConfig.getDefaultFormatters();
const formatters2 = this.formatterConfig.getDefaultFormatters();
// formatters1 and formatters2 contain the same instances

2. Lazy Initialization

Built-in formatters are created only when first accessed:
// Formatters are created on first call to getDefaultFormatters()
// Subsequent calls return the same instances

3. Conditional Execution

Formatters check shouldFormat() before applying regex:
export class MyFormatter extends CometChatTextFormatter {
  shouldFormat(text: string): boolean {
    // Quick check before expensive regex
    return text.includes('#');
  }
  
  format(text: string): string {
    // Only called if shouldFormat() returns true
    return text.replace(/#(\w+)/g, ...);
  }
}

Integration with Components

The service integrates seamlessly with all text-displaying components:

Text Bubble

export class CometChatTextBubble implements OnInit {
  @Input() textFormatters?: CometChatTextFormatter[];
  
  private formatterConfig = inject(FormatterConfigService);

  ngOnInit() {
    const formatters = this.textFormatters || 
                       this.formatterConfig.getDefaultFormatters();
    this.applyFormatters(formatters);
  }
}

Message List

export class CometChatMessageList implements OnInit {
  @Input() textFormatters?: CometChatTextFormatter[];
  
  private formatterConfig = inject(FormatterConfigService);

  ngOnInit() {
    const formatters = this.textFormatters || 
                       this.formatterConfig.getDefaultFormatters();
    // Pass to all child text bubbles
  }
}

Conversations

export class CometChatConversations implements OnInit {
  @Input() textFormatters?: CometChatTextFormatter[];
  
  private formatterConfig = inject(FormatterConfigService);

  ngOnInit() {
    const formatters = this.textFormatters || 
                       this.formatterConfig.getDefaultFormatters();
    // Pass to all conversation items
  }
}

Best Practices

1. Configure Once, Use Everywhere

Set formatters globally in app initialization:
// ✅ GOOD: Configure once in AppComponent
export class AppComponent implements OnInit {
  private formatterConfig = inject(FormatterConfigService);

  ngOnInit() {
    this.formatterConfig.addFormatters([
      new HashtagFormatter(),
      new EmailFormatter()
    ]);
  }
}

// All components automatically use these formatters

2. Use Context-Aware Formatting

Always provide logged-in user for self-mention detection:
// ✅ GOOD: Provide context
const loggedInUser = CometChat.getLoggedInUser();
const formatters = this.formatterConfig.getFormattersWithContext(
  loggedInUser,
  alignment
);

// ❌ BAD: No context
const formatters = this.formatterConfig.getDefaultFormatters();
// Self-mentions won't be detected

3. Respect Formatter Priority

Set appropriate priority values for custom formatters:
// ✅ GOOD: Logical priority
export class HashtagFormatter extends CometChatTextFormatter {
  constructor() {
    super();
    this.priority = 50; // Between mentions (20) and URLs (100)
  }
}

// ❌ BAD: Same priority as built-in
export class HashtagFormatter extends CometChatTextFormatter {
  constructor() {
    super();
    this.priority = 20; // Conflicts with mentions formatter
  }
}

4. Implement shouldFormat()

Optimize performance with quick checks:
// ✅ GOOD: Quick check before regex
shouldFormat(text: string): boolean {
  return text.includes('#');
}

// ❌ BAD: Always returns true
shouldFormat(text: string): boolean {
  return true; // Regex always runs
}

5. Handle Edge Cases

Ensure formatters handle empty or invalid input:
// ✅ GOOD: Defensive programming
format(text: string): string {
  if (!text || text.trim().length === 0) {
    return text;
  }
  
  return text.replace(this.getRegex(), ...);
}

Common Patterns

Pattern: Feature Flag for Formatters

Enable/disable formatters based on feature flags:
export class AppComponent implements OnInit {
  private formatterConfig = inject(FormatterConfigService);

  ngOnInit() {
    const formatters: CometChatTextFormatter[] = [
      new CometChatMentionsFormatter(),
      new CometChatUrlFormatter()
    ];
    
    // Add optional formatters based on feature flags
    if (this.featureFlags.hashtagsEnabled) {
      formatters.push(new HashtagFormatter());
    }
    
    if (this.featureFlags.emailDetectionEnabled) {
      formatters.push(new EmailFormatter());
    }
    
    this.formatterConfig.setDefaultFormatters(formatters);
  }
}

Pattern: User Preference for Formatters

Allow users to customize formatter behavior:
export class SettingsComponent {
  private formatterConfig = inject(FormatterConfigService);

  saveFormatterPreferences(preferences: FormatterPreferences) {
    const formatters: CometChatTextFormatter[] = [];
    
    if (preferences.enableMentions) {
      formatters.push(new CometChatMentionsFormatter());
    }
    
    if (preferences.enableUrls) {
      formatters.push(new CometChatUrlFormatter());
    }
    
    if (preferences.enableHashtags) {
      formatters.push(new HashtagFormatter());
    }
    
    this.formatterConfig.setDefaultFormatters(formatters);
  }
}

Pattern: Formatter Testing

Test custom formatters in isolation:
describe('HashtagFormatter', () => {
  let formatter: HashtagFormatter;

  beforeEach(() => {
    formatter = new HashtagFormatter();
  });

  it('should format hashtags', () => {
    const input = 'Check out #angular and #typescript';
    const output = formatter.format(input);
    
    expect(output).toContain('class="cometchat-hashtag"');
    expect(output).toContain('data-tag="angular"');
    expect(output).toContain('data-tag="typescript"');
  });

  it('should not format without hashtags', () => {
    const input = 'No hashtags here';
    expect(formatter.shouldFormat(input)).toBe(false);
  });
});

Troubleshooting

Formatters Not Applied

Problem: Text is not being formatted. Solution: Check that formatters are configured:
// Debug: Log formatters
const formatters = this.formatterConfig.getDefaultFormatters();
console.log('Active formatters:', formatters);

// Verify formatters are passed to components
<cometchat-text-bubble [textFormatters]="formatters"></cometchat-text-bubble>

Self-Mentions Not Detected

Problem: All mentions show as “other” mentions. Solution: Provide logged-in user context:
// ❌ BAD: No context
const formatters = this.formatterConfig.getDefaultFormatters();

// ✅ GOOD: With context
const loggedInUser = CometChat.getLoggedInUser();
const formatters = this.formatterConfig.getFormattersWithContext(loggedInUser);

Custom Formatters Not Working

Problem: Custom formatters added but not executing. Solution: Check if custom formatters were set (which overrides additions):
// If you called setDefaultFormatters(), addFormatters() has no effect
this.formatterConfig.setDefaultFormatters([...]); // Overrides everything
this.formatterConfig.addFormatters([...]); // This won't work

// Solution: Include all formatters in setDefaultFormatters()
this.formatterConfig.setDefaultFormatters([
  new CometChatMentionsFormatter(),
  new CometChatUrlFormatter(),
  new MyCustomFormatter() // Include custom here
]);

Formatters Execute in Wrong Order

Problem: Formatters execute in unexpected order. Solution: Check priority values:
// Verify priorities
const formatters = this.formatterConfig.getDefaultFormatters();
formatters.forEach(f => {
  console.log(`${f.constructor.name}: priority ${f.priority}`);
});

// Adjust custom formatter priority
export class MyFormatter extends CometChatTextFormatter {
  constructor() {
    super();
    this.priority = 50; // Set appropriate value
  }
}

Scoping for Multiple Instances

FormatterConfigService is provided at the root level (providedIn: 'root'), so all components share the same formatter configuration by default. If you need different formatters for different message lists (e.g., a main chat with full formatting vs. a thread panel with minimal formatting), scope the service to a wrapper component:
@Component({
  selector: 'app-thread-panel',
  standalone: true,
  imports: [CometChatMessageListComponent],
  providers: [FormatterConfigService], // Scoped instance
  template: `<cometchat-message-list [user]="user" [parentMessageId]="parentMessageId"></cometchat-message-list>`
})
export class ThreadPanelComponent implements OnInit {
  // This is the LOCAL instance, not the root singleton
  private formatterConfig = inject(FormatterConfigService);

  ngOnInit(): void {
    // Only affects text rendering inside this wrapper
    // e.g., thread panel uses only URL formatting, no mentions
    this.formatterConfig.setDefaultFormatters([
      new CometChatUrlFormatter()
    ]);
  }
}
Angular’s hierarchical DI ensures the message list inside the wrapper resolves the local FormatterConfigService instance. The main chat panel continues to use the root singleton. See CometChatMessageList — Multiple Message Lists with Different Configurations for a complete multi-panel example.

See Also