@centigrade/ai-chat-angular-service
v1.6.2
Published
Angular service that wraps the <ai-chat> Stencil web component. Provides Angular-idiomatic signals and observables for component state, tool-call handling, and full lifecycle management of the Centigrade AI Chat middleware.
Readme
@centigrade/ai-chat-angular-service
An Angular service that integrates the Centigrade AI Chat Widget into your Angular application. It wraps the <ai-chat> web component in an Angular-idiomatic API built on typed signals, observables, and a clean lifecycle model.
⚠️ Prerequisites — Backend Required
This package is the client-side Angular integration layer for the Centigrade AI Chat platform. It requires a dedicated backend middleware deployed and configured by Centigrade for your organization.
Without an active backend deployment you will not be able to establish a connection. The following configuration values are provided by Centigrade when your instance is set up:
Installation
npm install @centigrade/ai-chat-angular-servicePeer dependencies (must already be present in your Angular project):
@angular/common ^22.0.0
@angular/core ^22.0.0What this service enables
Once connected to the Centigrade backend, the <ai-chat> widget gives your users a fully-featured AI chat experience directly inside your Angular app. The service layer on top adds:
- Angular signals for
available,componentState, andisOwned— no async pipes or manual subscriptions needed in templates. - Observable-based tool-call handling so your application can respond to AI-initiated function calls (e.g. navigating to a page, fetching live data).
- Automatic element adoption — if
<ai-chat>is already in the DOM (placed inindex.html), the service detects and manages it without duplicating the element. - Runtime property setters — update the JWT token, language, or any other configuration property reactively after initial mount.
- Lifecycle safety —
destroy()cleanly removes listeners, cancels pending tool calls, and resets all signals.
Quick Start
Option A — Let the service create the element
import { Component, ElementRef, inject, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { filter } from 'rxjs/operators';
import { AiChatService, CommunicationMode } from '@centigrade/ai-chat-angular-service';
@Component({
selector: 'app-shell',
template: `
<!-- host container for the <ai-chat> element -->
<div #chatHost></div>
<!-- reactive state — no async pipe needed -->
<p>Chat state: {{ chatService.componentState() }}</p>
`,
})
export class ShellComponent implements OnInit, OnDestroy {
@ViewChild('chatHost', { static: true }) chatHost!: ElementRef<HTMLDivElement>;
protected readonly chatService = inject(AiChatService);
ngOnInit(): void {
// Mount the widget and load the component script
this.chatService.inject(
this.chatHost.nativeElement,
{
communicationMode: CommunicationMode.MULTIPLE_ASSISTANT_BASED,
middlewareBaseUrl: 'https://your-backend.example.com', // provided by Centigrade
language: 'en_US',
jwtEnabled: true,
},
'https://cdn.example.com/v1/build/ai-chat-component.esm.js', // provided by Centigrade
);
// Supply the user's JWT after login
this.chatService.setToken(myAuthService.getToken());
// Handle AI-initiated function calls.
//
// Option 1a: toolCallFor(name) — single function, null already excluded.
this.chatService.toolCallFor('navigateTo').subscribe((request) => {
const { route } = request.dto.functionArgs as { route: string };
this.router.navigateByUrl(route);
request.respond(JSON.stringify({ success: true, navigatedTo: route }));
});
// Option 1b: toolCallFor([...names]) — handle several functions in one stream.
this.chatService.toolCallFor(['navigateTo', 'openDialog']).subscribe((request) => {
const { functionName, functionArgs } = request.dto;
switch (functionName) {
case 'navigateTo': {
const { route } = functionArgs as { route: string };
this.router.navigateByUrl(route);
request.respond(JSON.stringify({ success: true, navigatedTo: route }));
break;
}
case 'openDialog': {
this.dialog.open(functionArgs['dialogId'] as string);
request.respond(JSON.stringify({ success: true }));
break;
}
}
});
// Option 2: toolCall$ with filter(Boolean) — catch-all including a default branch.
this.chatService.toolCall$.pipe(filter(Boolean)).subscribe((request) => {
const { functionName, functionArgs } = request.dto;
switch (functionName) {
case 'navigateTo': {
const { route } = functionArgs as { route: string };
this.router.navigateByUrl(route);
request.respond(JSON.stringify({ success: true, navigatedTo: route }));
break;
}
case 'getCurrentUser': {
const user = this.authService.currentUser();
request.respond(JSON.stringify({ name: user.name, role: user.role }));
break;
}
default:
request.respond(JSON.stringify({ error: `Unknown function: ${functionName}` }));
}
});
}
ngOnDestroy(): void {
this.chatService.destroy();
}
}Option B — Element already in index.html
If <ai-chat> is embedded statically in your HTML (as configured in the Centigrade integration guide), the service detects it automatically at startup — no inject() call required for mounting.
ngOnInit(): void {
// Element already in the DOM — just subscribe to events
this.chatService.toolCall$.subscribe(request => { /* ... */ });
this.chatService.setToken(myAuthService.getToken());
}
ngOnDestroy(): void {
this.chatService.destroy(); // detaches listeners only; element stays in the DOM
}API Reference
AiChatService
Lifecycle
| Method | Signature | Description |
| --------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| inject | (container: HTMLElement, config: AiChatConfig, scriptSrc: string) => void | Creates (or adopts) <ai-chat>, applies configuration, and loads the component script. |
| destroy | () => void | Detaches event listeners, cancels pending tool calls, and resets all signals. Removes the element from the DOM only if the service owns it (Option A). |
Signals
| Property | Type | Initial value | Description |
| ---------------- | ------------------------ | --------------- | ------------------------------------------------------------ |
| available | Signal<boolean> | false | true while an <ai-chat> element is attached and managed. |
| componentState | Signal<ComponentState> | 'notInjected' | Current state forwarded from the <ai-chat> element. |
| isOwned | Signal<boolean> | false | true when the service created the element (Option A). |
Observables
| Property | Type | Description |
| ----------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| toolCall$ | Observable<ToolCallRequest \| null> | Emits each time the AI requests a client-side function call. Emits null after a call is answered or times out. |
| componentState$ | Observable<ComponentState> | Observable counterpart to the componentState signal. |
Runtime setters
| Method | Description |
| -------------------------------------- | ------------------------------------------------------------------------------- |
| setToken(token: string) | Updates the JWT bearer token and triggers a reconnection. |
| setUserContext(context: UserContext) | Sets structured user context (role, name, …) forwarded to the AI every request. |
| setLanguage(lang: LANG_KEY_KEYS) | Switches the UI language at runtime. |
| setProperty(key, value) | Generic setter for any AiChatConfig property. |
| toggleChatView(open?: boolean) | Opens or closes the chat window. Omit the argument to toggle. |
| answerToolCall(toolCallId, result) | Responds to a specific pending tool call by ID. |
Tool call handling
When the AI backend requests a client-side function call it emits a ToolCallRequest on toolCall$:
interface ToolCallRequest {
dto: {
toolCallId: string;
functionName: string;
functionArgs: Record<string, unknown>;
};
respond(result: string): void; // call this with a JSON-stringified return value
}If respond() is not called within the configured timeout (toolCallTimeoutMs, default 10 s) the service automatically responds with an error so the conversation can continue.
AiChatConfig
interface AiChatConfig {
communicationMode: CommunicationMode; // required
middlewareBaseUrl?: string;
assistantId?: string;
sessionId?: string | null;
language?: LANG_KEY_KEYS; // e.g. 'en_US' | 'de_DE' | …
jwtEnabled?: boolean;
jwtToken?: string;
overwriteChatTitle?: string;
assistantIcon?: string;
overwriteInputPlaceholderText?: string;
overwriteAssistantSelectionTitle?: string;
overwriteButtonIcon?: string;
overwriteAvatar?: string;
useOverwriteIcons?: boolean;
disableMenuButton?: boolean;
disableToggleThemeButton?: boolean;
disableChatToggleButton?: boolean;
toolCallTimeoutMs?: number; // default: 10 000
/** Structured user context forwarded to the AI with every request. */
userContext?: UserContext; // e.g. { userId, name, role, department }
}ComponentState
type ComponentState =
| 'notInjected' // before inject() is called
| 'initializing' // element created, connecting
| 'jwtTokenNotSet'
| 'jwtTokenInvalid'
| 'jwtTokenExpired'
| 'connecting'
| 'connected'
| 'ready' // fully operational
| 'disconnected'
| 'notReady'
| 'error';