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.
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.
| Signal | Resets on ChatComponent.clearThread()? |
|---|---|
componentReady | no |
firstMessageSent | no (sticky for life of <chat>) |
messageCount | yes (to 0) |
inputSubmittedAt | yes (to null) |