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 inDioscService - BYOA authentication — Reading tokens from
localStoragein the auth callback - Navigation tool — Using Angular Router's
navigateByUrl()for in-app navigation - Navigation observation — Subscribing to
Router.events(NavigationEnd) and callingdiosc('observe', 'navigation', ...) - Connect/disconnect — Reacting to authentication state changes via RxJS
BehaviorSubject
Key files
| File | Purpose |
|---|---|
services/diosc.service.ts | Full Diosc SDK integration service |
services/auth.service.ts | Authentication with RxJS observables |
guards/auth.guard.ts | Route guard with role-based access |
app.component.ts | Root component rendering <diosc-chat> |
environments/environment.ts | Diosc 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
- Vanilla JS Integration - No-framework approach
- Common Integration Patterns - Framework-agnostic patterns
- Styling - Customize appearance