Page actions

Chat Lifecycle Signals

@threadplane/chat exposes per-instance lifecycle signals via the CHAT_LIFECYCLE injection token. Subscribe to them for debugging, custom dashboards, or telemetry integrations.

Warning: The token is scoped to one chat instance

ChatComponent provides CHAT_LIFECYCLE in its own providers array, so the signals describe that one <chat> element and are reachable only from its injector: content you project into <chat>, or a component <chat> itself creates. A component elsewhere in the tree calling inject(CHAT_LIFECYCLE) hits a NullInjectorError.

Interface

import { InjectionToken, Signal } from '@angular/core';
 
export interface ChatLifecycle {
  /** True after <chat> initializes with a non-null agent binding. */
  readonly componentReady: Signal<boolean>;
  /** True after the first user submit. Sticky for the life of the chat instance — does NOT reset on clearThread. */
  readonly firstMessageSent: Signal<boolean>;
  /** Count of user submits. Resets on clearThread. */
  readonly messageCount: Signal<number>;
  /** Epoch ms of the most recent user submit. Resets on clearThread. */
  readonly inputSubmittedAt: Signal<number | null>;
}
 
export const CHAT_LIFECYCLE = new InjectionToken<ChatLifecycle>('CHAT_LIFECYCLE');

Subscribing

Inject the token from a component projected into <chat> — a header, a welcome suggestion strip, a footer badge:

import { Component, inject, effect } from '@angular/core';
import { CHAT_LIFECYCLE } from '@threadplane/chat';
 
@Component({
  selector: 'app-chat-header',
  standalone: true,
  template: `<span>{{ lifecycle.messageCount() }} sent</span>`,
})
export class ChatHeaderComponent {
  protected readonly lifecycle = inject(CHAT_LIFECYCLE);
 
  constructor() {
    effect(() => {
      if (this.lifecycle.firstMessageSent()) {
        console.log('User sent their first message at', this.lifecycle.inputSubmittedAt());
      }
    });
  }
}
<chat [agent]="agent">
  <app-chat-header chatHeader />
</chat>

Reading the signals from outside <chat>

A component that is not inside a <chat> injector has two options. Inject optionally and handle the absent case:

private readonly lifecycle = inject(CHAT_LIFECYCLE, { optional: true });

Or hold a view reference to the <chat> element and read the token off its injector:

import { Component, Injector, viewChild } from '@angular/core';
import { injectAgent } from '@threadplane/langgraph';
import { CHAT_LIFECYCLE, ChatComponent } from '@threadplane/chat';
 
@Component({
  selector: 'app-chat-page',
  standalone: true,
  imports: [ChatComponent],
  template: `<chat #surface [agent]="agent" />`,
})
export class ChatPageComponent {
  protected readonly agent = injectAgent();
  private readonly surface = viewChild.required('surface', { read: Injector });
 
  protected lifecycle() {
    return this.surface().get(CHAT_LIFECYCLE);
  }
}

Reset semantics

ChatComponent.clearThread() is the public method that resets local view state for a new thread. Call it on a <chat> view reference when you switch threads yourself.

SignalResets on ChatComponent.clearThread()?
componentReadyno
firstMessageSentno (sticky for life of <chat>)
messageCountyes (to 0)
inputSubmittedAtyes (to null)

Looking for something specific?