Skip to main content

Angular Integration

This guide covers integrating Diosc into Angular applications, including services, modules, and Angular Universal SSR.

Basic Setup

Installation

npm install @diosc-ai/assistant-kit
npm install -D @types/diosc-client

Configure Custom Elements Schema

Tell Angular to allow diosc-* elements in app.module.ts:

import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA], // Allow web components
bootstrap: [AppComponent]
})
export class AppModule {}

For standalone components (Angular 14+):

@Component({
selector: 'app-root',
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `<diosc-chat />`
})
export class AppComponent {}

Basic Component

import { Component, OnInit, OnDestroy } from '@angular/core';
import { diosc } from '@diosc-ai/assistant-kit';
import { environment } from '../environments/environment';

@Component({
selector: 'app-root',
template: `
<div class="app">
<h1>My Application</h1>
<diosc-chat></diosc-chat>
</div>
`
})
export class AppComponent implements OnInit, OnDestroy {
ngOnInit() {
diosc('config', {
backendUrl: environment.dioscUrl,
apiKey: environment.dioscApiKey,
autoConnect: true
});
}

ngOnDestroy() {
diosc('disconnect');
}
}

Diosc Service

Create a service to manage Diosc across your application:

// services/diosc.service.ts
import { Injectable, OnDestroy } from '@angular/core';
import { BehaviorSubject, Subject } from 'rxjs';
import { diosc } from '@diosc-ai/assistant-kit';
import type { DioscMessage } from '@types/diosc-client';

@Injectable({
providedIn: 'root'
})
export class DioscService implements OnDestroy {
private unsubscribers: (() => void)[] = [];

// Observables for components to subscribe to
private connectedSubject = new BehaviorSubject<boolean>(false);
private messagesSubject = new BehaviorSubject<DioscMessage[]>([]);
private loadingSubject = new BehaviorSubject<boolean>(false);
private errorSubject = new Subject<Error>();

isConnected$ = this.connectedSubject.asObservable();
messages$ = this.messagesSubject.asObservable();
isLoading$ = this.loadingSubject.asObservable();
error$ = this.errorSubject.asObservable();

initialize(backendUrl: string, apiKey: string) {
diosc('config', {
backendUrl,
apiKey,
autoConnect: true
});

this.setupEventListeners();
}

private setupEventListeners() {
this.unsubscribers = [
diosc('on', 'connected', () => {
this.connectedSubject.next(true);
}),

diosc('on', 'disconnected', () => {
this.connectedSubject.next(false);
}),

diosc('on', 'message', (msg: DioscMessage) => {
const current = this.messagesSubject.value;
this.messagesSubject.next([...current, msg]);
this.loadingSubject.next(false);
}),

diosc('on', 'streaming_start', () => {
this.loadingSubject.next(true);
}),

diosc('on', 'error', (err: Error) => {
this.errorSubject.next(err);
this.loadingSubject.next(false);
})
];
}

setAuth(getHeaders: () => Promise<Record<string, string>>) {
diosc('auth', async () => ({
headers: await getHeaders()
}));
}

updatePageContext(context: Record<string, any>) {
diosc('pageContext', context);
}

sendMessage(content: string) {
const current = this.messagesSubject.value;
this.messagesSubject.next([...current, { role: 'user', content }]);
this.loadingSubject.next(true);
diosc('send', content);
}

clearMessages() {
this.messagesSubject.next([]);
}

ngOnDestroy() {
this.unsubscribers.forEach(unsub => unsub());
diosc('disconnect');
}
}

Using the Service

// app.component.ts
import { Component, OnInit } from '@angular/core';
import { DioscService } from './services/diosc.service';
import { AuthService } from './services/auth.service';
import { environment } from '../environments/environment';

@Component({
selector: 'app-root',
template: `
<app-header></app-header>
<router-outlet></router-outlet>
<diosc-chat></diosc-chat>
`
})
export class AppComponent implements OnInit {
constructor(
private dioscService: DioscService,
private authService: AuthService
) {}

ngOnInit() {
// Initialize Diosc
this.dioscService.initialize(
environment.dioscUrl,
environment.dioscApiKey
);

// Set up authentication
this.dioscService.setAuth(async () => ({
'Authorization': `Bearer ${await this.authService.getAccessToken()}`
}));
}
}

Page Context with Router

Update page context on route changes:

// services/diosc-router.service.ts
import { Injectable } from '@angular/core';
import { Router, NavigationEnd, ActivatedRoute } from '@angular/router';
import { filter } from 'rxjs/operators';
import { DioscService } from './diosc.service';

@Injectable({
providedIn: 'root'
})
export class DioscRouterService {
constructor(
private router: Router,
private route: ActivatedRoute,
private dioscService: DioscService
) {
this.router.events.pipe(
filter(event => event instanceof NavigationEnd)
).subscribe((event: NavigationEnd) => {
this.updateContext(event.urlAfterRedirects);
});
}

private updateContext(url: string) {
// Get route data
let currentRoute = this.route.root;
while (currentRoute.firstChild) {
currentRoute = currentRoute.firstChild;
}

const routeData = currentRoute.snapshot.data;

this.dioscService.updatePageContext({
path: url,
pageType: routeData['pageType'],
params: currentRoute.snapshot.params
});
}
}

Initialize in app.component.ts:

constructor(
private dioscService: DioscService,
private dioscRouterService: DioscRouterService // Just inject to initialize
) {}

Custom Chat Component

Build your own chat interface:

// components/custom-chat/custom-chat.component.ts
import { Component, OnInit, OnDestroy, ViewChild, ElementRef, AfterViewChecked } from '@angular/core';
import { Subscription } from 'rxjs';
import { DioscService } from '../../services/diosc.service';
import type { DioscMessage } from '@types/diosc-client';

@Component({
selector: 'app-custom-chat',
template: `
<div class="chat-container">
<div class="messages" #messagesContainer>
<div
*ngFor="let msg of messages"
[ngClass]="['message', msg.role]"
>
<div class="content" [innerHTML]="renderMarkdown(msg.content)"></div>
</div>
<div *ngIf="isLoading" class="message assistant loading">
<span class="typing-indicator">...</span>
</div>
</div>

<form (ngSubmit)="sendMessage()" class="input-form">
<input
[(ngModel)]="inputText"
name="message"
type="text"
placeholder="Type a message..."
[disabled]="!isConnected"
/>
<button
type="submit"
[disabled]="!inputText.trim() || isLoading"
>
Send
</button>
</form>

<!-- Headless agent -->
<diosc-agent></diosc-agent>
</div>
`,
styleUrls: ['./custom-chat.component.scss']
})
export class CustomChatComponent implements OnInit, OnDestroy, AfterViewChecked {
@ViewChild('messagesContainer') messagesContainer!: ElementRef;

messages: DioscMessage[] = [];
isConnected = false;
isLoading = false;
inputText = '';

private subscriptions: Subscription[] = [];

constructor(private dioscService: DioscService) {}

ngOnInit() {
this.subscriptions = [
this.dioscService.messages$.subscribe(msgs => {
this.messages = msgs;
}),
this.dioscService.isConnected$.subscribe(connected => {
this.isConnected = connected;
}),
this.dioscService.isLoading$.subscribe(loading => {
this.isLoading = loading;
})
];
}

ngAfterViewChecked() {
this.scrollToBottom();
}

ngOnDestroy() {
this.subscriptions.forEach(sub => sub.unsubscribe());
}

sendMessage() {
if (!this.inputText.trim()) return;
this.dioscService.sendMessage(this.inputText);
this.inputText = '';
}

renderMarkdown(content: string): string {
// Use a markdown library like marked
return content; // Replace with actual markdown rendering
}

private scrollToBottom() {
const container = this.messagesContainer?.nativeElement;
if (container) {
container.scrollTop = container.scrollHeight;
}
}
}
// custom-chat.component.scss
.chat-container {
display: flex;
flex-direction: column;
height: 100%;
}

.messages {
flex: 1;
overflow-y: auto;
padding: 1rem;
}

.message {
margin-bottom: 1rem;
padding: 0.75rem 1rem;
border-radius: 8px;
max-width: 80%;

&.user {
background: #0066cc;
color: white;
margin-left: auto;
}

&.assistant {
background: #f0f0f0;
}
}

.input-form {
display: flex;
padding: 1rem;
border-top: 1px solid #e0e0e0;

input {
flex: 1;
padding: 0.75rem;
border: 1px solid #e0e0e0;
border-radius: 4px;
margin-right: 0.5rem;
}

button {
padding: 0.75rem 1.5rem;
background: #0066cc;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;

&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
}

.typing-indicator {
animation: blink 1s infinite;
}

@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0.3; }
}

Angular Universal (SSR)

Diosc requires browser APIs and won't work during SSR.

Platform Check

import { Component, OnInit, PLATFORM_ID, Inject } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

@Component({
selector: 'app-root',
template: `
<diosc-chat *ngIf="isBrowser"></diosc-chat>
`
})
export class AppComponent implements OnInit {
isBrowser: boolean;

constructor(@Inject(PLATFORM_ID) platformId: Object) {
this.isBrowser = isPlatformBrowser(platformId);
}

ngOnInit() {
if (this.isBrowser) {
// Initialize Diosc only in browser
import('@diosc-ai/assistant-kit').then(({ diosc }) => {
diosc('config', { ... });
});
}
}
}

Lazy-Loaded Module

Create a module that only loads in the browser:

// diosc.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';

@NgModule({
imports: [CommonModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class DioscModule {
constructor() {
// Dynamic import ensures this only runs in browser
import('@diosc-ai/assistant-kit');
}
}

Event Handling

Using ElementRef

import { Component, ViewChild, ElementRef, AfterViewInit, OnDestroy } from '@angular/core';

@Component({
selector: 'app-chat-wrapper',
template: `<diosc-chat #chatElement></diosc-chat>`
})
export class ChatWrapperComponent implements AfterViewInit, OnDestroy {
@ViewChild('chatElement') chatElement!: ElementRef;

private boundHandler = this.handleMessage.bind(this);

ngAfterViewInit() {
this.chatElement.nativeElement.addEventListener('dioscMessage', this.boundHandler);
}

ngOnDestroy() {
this.chatElement.nativeElement.removeEventListener('dioscMessage', this.boundHandler);
}

private handleMessage(event: CustomEvent) {
console.log('Message received:', event.detail);
}
}

RxJS Integration

Convert Diosc events to RxJS observables:

// services/diosc-events.service.ts
import { Injectable, OnDestroy } from '@angular/core';
import { Observable, fromEventPattern } from 'rxjs';
import { diosc } from '@diosc-ai/assistant-kit';

@Injectable({
providedIn: 'root'
})
export class DioscEventsService {
message$: Observable<any>;
toolStart$: Observable<any>;
toolEnd$: Observable<any>;
error$: Observable<any>;

constructor() {
this.message$ = this.createEventObservable('message');
this.toolStart$ = this.createEventObservable('tool_start');
this.toolEnd$ = this.createEventObservable('tool_end');
this.error$ = this.createEventObservable('error');
}

private createEventObservable(eventName: string): Observable<any> {
return fromEventPattern(
(handler) => diosc('on', eventName, handler),
(handler, unsubscribe) => unsubscribe()
);
}
}

Usage:

@Component({ ... })
export class MyComponent implements OnInit, OnDestroy {
private subscription!: Subscription;

constructor(private dioscEvents: DioscEventsService) {}

ngOnInit() {
this.subscription = this.dioscEvents.message$.subscribe(msg => {
console.log('New message:', msg);
});
}

ngOnDestroy() {
this.subscription.unsubscribe();
}
}

Standalone Components (Angular 14+)

import { Component, CUSTOM_ELEMENTS_SCHEMA, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { diosc } from '@diosc-ai/assistant-kit';

@Component({
selector: 'app-diosc-chat',
standalone: true,
imports: [CommonModule, FormsModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<div class="chat-wrapper">
<diosc-chat></diosc-chat>
</div>
`
})
export class DioscChatComponent implements OnInit {
ngOnInit() {
diosc('config', {
backendUrl: 'https://your-hub.example.com',
apiKey: 'your-api-key'
});
}
}

Environment Configuration

// environments/environment.ts
export const environment = {
production: false,
dioscUrl: 'http://localhost:3000',
dioscApiKey: 'dev-api-key'
};

// environments/environment.prod.ts
export const environment = {
production: true,
dioscUrl: 'https://your-hub.example.com',
dioscApiKey: 'prod-api-key'
};

Troubleshooting

'diosc-chat' is not a known element

Add CUSTOM_ELEMENTS_SCHEMA to your module or component:

@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})

SSR Build Errors

Wrap browser-only code with platform checks:

if (isPlatformBrowser(this.platformId)) {
// Browser-only code
}

Zone.js Issues

Diosc events may run outside Angular's zone:

import { NgZone } from '@angular/core';

constructor(private ngZone: NgZone) {}

ngOnInit() {
diosc('on', 'message', (msg) => {
this.ngZone.run(() => {
this.messages.push(msg);
});
});
}

Memory Leaks

Always clean up subscriptions and event listeners:

ngOnDestroy() {
this.subscriptions.forEach(sub => sub.unsubscribe());
this.unsubscribers.forEach(unsub => unsub());
diosc('disconnect');
}

Real-World Example: ACME Helpdesk

The samples/acme-helpdesk/packages/ng-app/ directory contains a complete Angular 19 integration demonstrating:

  • Diosc SDK via loadDiosc() — Script loading and configuration in DioscService
  • BYOA authentication — Reading tokens from localStorage in the auth callback
  • Navigation tool — Using Angular Router's navigateByUrl() for in-app navigation
  • Navigation observation — Subscribing to Router.events (NavigationEnd) and calling diosc('observe', 'navigation', ...)
  • Connect/disconnect — Reacting to authentication state changes via RxJS BehaviorSubject

Key files

FilePurpose
services/diosc.service.tsFull Diosc SDK integration service
services/auth.service.tsAuthentication with RxJS observables
guards/auth.guard.tsRoute guard with role-based access
app.component.tsRoot component rendering <diosc-chat>
environments/environment.tsDiosc Hub URL, API key, assistant ID

Running the sample

cd samples/acme-helpdesk
npm install
npm run dev:api # API on port 3003
npm run dev:ng # Angular on port 3005

Next Steps