Skip to main content

Overview

ChatStateService is the centralized Angular service for managing active chat state across the CometChat Angular UIKit. It provides a single source of truth for the currently active chat entity (User, Group, or Conversation) in your application. The service is provided at the root level (providedIn: 'root') and is available throughout your application via Angular dependency injection.

Hybrid Approach

ChatStateService implements the Hybrid Approach pattern:
  1. Service-based (default) — Components automatically subscribe to ChatStateService and react to state changes.
  2. Props override — When @Input() bindings are provided, they take priority over service state for that component instance.
This gives you zero-config wiring out of the box while preserving full control when you need it.

Import

import { inject } from '@angular/core';
import { ChatStateService } from '@cometchat/chat-uikit-angular';

export class MyComponent {
  private chatStateService = inject(ChatStateService);
}

Signals (Readonly)

ChatStateService exposes Angular Signals for synchronous, fine-grained reactivity with automatic dependency tracking and efficient change detection.
SignalTypeDescription
activeUserSignal<CometChat.User | null>Readonly signal for the currently active user. Returns null when no user is active.
activeGroupSignal<CometChat.Group | null>Readonly signal for the currently active group. Returns null when no group is active.
activeConversationSignal<CometChat.Conversation | null>Readonly signal for the currently active conversation. Returns null when no conversation is active.

Observables

For RxJS-based reactivity and integration with existing observable-based code, ChatStateService provides Observable streams derived from the underlying signals via toObservable().
ObservableTypeDescription
activeUser$Observable<CometChat.User | null>Observable stream that emits whenever the active user changes.
activeGroup$Observable<CometChat.Group | null>Observable stream that emits whenever the active group changes.
activeConversation$Observable<CometChat.Conversation | null>Observable stream that emits whenever the active conversation changes.

Setter Methods

Setter methods update the active chat state. They enforce mutual exclusivity — only one chat entity (User or Group) can be active at a time.
MethodParametersReturn TypeDescription
setActiveUser(user)user: CometChat.User | nullvoidSets the active user. If user is non-null, automatically clears the active group (mutual exclusivity).
setActiveGroup(group)group: CometChat.Group | nullvoidSets the active group. If group is non-null, automatically clears the active user (mutual exclusivity).
setActiveConversation(conversation)conversation: CometChat.Conversation | nullvoidSets the active conversation and extracts the conversationWith entity. If the entity is a CometChat.User, calls setActiveUser(); if a CometChat.Group, calls setActiveGroup(). Passing null clears user and group.
clearActiveChat()voidClears all active state: user, group, and conversation are set to null. Also clears the internal active conversation in ConversationsService.

Getter Methods (Snapshots)

Getter methods return the current value of a signal as a one-time read without subscribing to changes. Use these in event handlers or one-time operations.
MethodReturn TypeDescription
getActiveUser()CometChat.User | nullReturns the current active user, or null if no user is active.
getActiveGroup()CometChat.Group | nullReturns the current active group, or null if no group is active.
getActiveConversation()CometChat.Conversation | nullReturns the current active conversation, or null if no conversation is active.
getActiveChatEntity()CometChat.User | CometChat.Group | nullReturns whichever entity is currently active (user takes precedence). Returns null if neither is active.

Mutual Exclusivity

ChatStateService enforces that only one chat entity — a CometChat.User or a CometChat.Group — can be active at any given time:
  • Calling setActiveUser(user) with a non-null value automatically sets activeGroup to null.
  • Calling setActiveGroup(group) with a non-null value automatically sets activeUser to null.
  • Calling setActiveConversation(conversation) extracts the entity and delegates to setActiveUser() or setActiveGroup(), which applies the same rule.
  • Calling clearActiveChat() resets all three signals to null.
This prevents ambiguous states and ensures components always know which entity they are working with.
Setting a user clears the group, and setting a group clears the user. Design your navigation flow with this in mind — you do not need to manually clear the previous entity before setting a new one.
import { inject } from '@angular/core';
import { ChatStateService } from '@cometchat/chat-uikit-angular';
import { CometChat } from '@cometchat/chat-sdk-javascript';

export class ChatNavigationComponent {
  private chatStateService = inject(ChatStateService);

  selectUser(user: CometChat.User): void {
    this.chatStateService.setActiveUser(user);

    // At this point:
    // activeUser()  → user
    // activeGroup() → null  (automatically cleared)
  }

  selectGroup(group: CometChat.Group): void {
    this.chatStateService.setActiveGroup(group);

    // At this point:
    // activeGroup() → group
    // activeUser()  → null  (automatically cleared)
  }
}

Usage Examples

Signals provide synchronous, fine-grained reactivity with automatic dependency tracking. This is the recommended approach for new code.
Signals are the preferred API for reading chat state. They integrate natively with Angular’s change detection and require no manual subscription management.
import { Component, inject, computed } from '@angular/core';
import { ChatStateService } from '@cometchat/chat-uikit-angular';
import { CometChat } from '@cometchat/chat-sdk-javascript';

@Component({
  selector: 'app-chat-panel',
  template: `
    @if (activeUser()) {
      <span>Chatting with: {{ activeUser()!.getName() }}</span>
    }
    @if (activeGroup()) {
      <span>Group: {{ activeGroup()!.getName() }}</span>
    }
    <p>{{ chatLabel() }}</p>
  `
})
export class ChatPanelComponent {
  private chatStateService = inject(ChatStateService);

  activeUser = this.chatStateService.activeUser;
  activeGroup = this.chatStateService.activeGroup;

  chatLabel = computed(() => {
    const user = this.chatStateService.activeUser();
    const group = this.chatStateService.activeGroup();
    if (user) return `1:1 chat with ${user.getName()}`;
    if (group) return `Group: ${group.getName()}`;
    return 'No active chat';
  });

  selectUser(user: CometChat.User): void {
    this.chatStateService.setActiveUser(user);
  }
}

Observable-Based

Observables integrate with existing RxJS-based code and the async pipe.
import { Component, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { ChatStateService } from '@cometchat/chat-uikit-angular';
import { CometChat } from '@cometchat/chat-sdk-javascript';

@Component({
  selector: 'app-chat-panel',
  imports: [AsyncPipe],
  template: `
    @if (chatStateService.activeUser$ | async; as user) {
      <span>Chatting with: {{ user.getName() }}</span>
    }
    @if (chatStateService.activeGroup$ | async; as group) {
      <span>Group: {{ group.getName() }}</span>
    }
  `
})
export class ChatPanelComponent {
  chatStateService = inject(ChatStateService);
}

Snapshot (One-Time Read)

Use getter methods when you need the current value once without subscribing to changes — for example, inside event handlers.
import { Component, inject } from '@angular/core';
import { ChatStateService } from '@cometchat/chat-uikit-angular';
import { CometChat } from '@cometchat/chat-sdk-javascript';

@Component({
  selector: 'app-message-actions',
  template: `<button (click)="sendMessage()">Send</button>`
})
export class MessageActionsComponent {
  private chatStateService = inject(ChatStateService);

  sendMessage(): void {
    const user = this.chatStateService.getActiveUser();
    const group = this.chatStateService.getActiveGroup();

    if (user) {
      console.log('Sending message to user:', user.getName());
    } else if (group) {
      console.log('Sending message to group:', group.getName());
    }
  }

  hasActiveChat(): boolean {
    return this.chatStateService.getActiveChatEntity() !== null;
  }
}

Hybrid Approach (Props Override Service)

Components can accept optional @Input() bindings that take priority over ChatStateService state. This lets you override the service for specific component instances while keeping the default wiring elsewhere.
When @Input() bindings are provided, they take priority over ChatStateService state for that component instance. Other components continue to read from the service.
import { Component, Input, inject, computed, signal } from '@angular/core';
import { ChatStateService } from '@cometchat/chat-uikit-angular';
import { CometChat } from '@cometchat/chat-sdk-javascript';

@Component({
  selector: 'app-message-header',
  template: `
    @if (effectiveUser()) {
      <h3>{{ effectiveUser()!.getName() }}</h3>
    }
  `
})
export class MessageHeaderComponent {
  private chatStateService = inject(ChatStateService);

  /** Optional prop override — if provided, takes priority over service state. */
  @Input() user?: CometChat.User;

  effectiveUser = computed(() =>
    this.user ?? this.chatStateService.activeUser()
  );
}
The following Chat-Aware Components integrate with ChatStateService. Each supports the Hybrid Approach — they auto-subscribe to the service by default and accept @Input() overrides.
ComponentKey InputsService Integration
CometChatConversationsCalls setActiveConversation() when a conversation is selected
CometChatMessageHeader[user], [group]Subscribes to activeUser / activeGroup
CometChatMessageList[user], [group]Subscribes to activeUser / activeGroup
CometChatMessageComposer[user], [group]Subscribes to activeUser / activeGroup
CometChatUsersCalls setActiveUser() when a user is selected
CometChatGroupsCalls setActiveGroup() when a group is selected
CometChatGroupMembers[group]Subscribes to activeGroup
CometChatThreadHeader[parentMessage]Receives parent message context from ChatStateService

See Also