🚀 Complete Integration Guide
📦 Installation
npm install @revrag-ai/embed-angular livekit-client lottie-web
# or
yarn add @revrag-ai/embed-angular livekit-client lottie-webPeer Dependencies
Ensure the following Angular packages are present in your project (they are usually already installed):
| Package | Version |
|---|---|
@angular/core | >=15.0.0 |
@angular/common | >=15.0.0 |
@angular/animations | >=15.0.0 |
@angular/router | >=15.0.0 |
livekit-client | ^2.0.0 |
lottie-web | ^5.10.0 |
⚠️ Important: CSS Import (REQUIRED)
The CSS file MUST be imported for the widget to display correctly. Without it, the widget will appear unstyled.
Option 1: angular.json / project.json (Recommended)
Add the path to the styles array in your angular.json (or project.json for Nx workspaces):
{
"projects": {
"your-app": {
"architect": {
"build": {
"options": {
"styles": [
"src/styles.css",
"node_modules/@revrag-ai/embed-angular/styles/widget.css"
]
}
}
}
}
}
}Option 2: Global styles.css Import
/* In your global styles.css */
@import '@revrag-ai/embed-angular/styles/widget.css';Option 3: Component-Level Import
/* In a component's .css / .scss file */
@import '@revrag-ai/embed-angular/styles/widget.css';⚡ Setup
Three one-time steps are required before placing any widget component.
Step 1 — Provide Animations
Angular animations must be enabled at the application level. Without this the widget transitions will not work.
Standalone bootstrap (app.config.ts):
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideAnimations } from '@angular/platform-browser/animations';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideAnimations(), // required
],
};NgModule bootstrap (app.module.ts):
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
@NgModule({
imports: [BrowserModule, BrowserAnimationsModule], // BrowserAnimationsModule required
})
export class AppModule {}Step 2 — Initialize the SDK
Call EmbedInitService.initialize() once, as early as possible — in your root AppComponent. It validates your API key, fetches widget configuration from Revrag's backend, and caches the result for the session.
// app.component.ts
import { Component, OnInit } from '@angular/core';
import { RouterModule } from '@angular/router';
import { EmbedInitService } from '@revrag-ai/embed-angular';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterModule],
template: `<router-outlet></router-outlet>`,
})
export class AppComponent implements OnInit {
constructor(private embedInit: EmbedInitService) {}
ngOnInit(): void {
this.embedInit.initialize('your-api-key');
}
}Get your API key from the Revrag dashboard.
🎯 Basic Usage
Fixed Positioning (Default)
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { EmbedButtonComponent, EmbedInitService } from '@revrag-ai/embed-angular';
@Component({
selector: 'app-my-page',
standalone: true,
imports: [CommonModule, EmbedButtonComponent],
template: `
<div>
<h1>My Application</h1>
<div *ngIf="isInitialized">
<!-- Widget will appear at bottom-right corner -->
<revrag-embed-button positioning="fixed"></revrag-embed-button>
</div>
</div>
`,
})
export class MyPageComponent implements OnInit {
isInitialized = false;
constructor(private embedInit: EmbedInitService) {}
ngOnInit(): void {
this.embedInit.isInitialized$.subscribe((ready) => {
this.isInitialized = ready;
});
}
}Embedded Positioning
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { EmbedButtonComponent, EmbedInitService } from '@revrag-ai/embed-angular';
@Component({
selector: 'app-help-section',
standalone: true,
imports: [CommonModule, EmbedButtonComponent],
template: `
<div style="position: relative; height: 500px; background: #f5f5f5; border-radius: 12px">
<h2>Need Help?</h2>
<p>Chat with our AI assistant</p>
<!-- Widget will appear at bottom-left of this container -->
<revrag-embed-button
*ngIf="isInitialized"
positioning="embedded"
side="left"
></revrag-embed-button>
</div>
`,
})
export class HelpSectionComponent implements OnInit {
isInitialized = false;
constructor(private embedInit: EmbedInitService) {}
ngOnInit(): void {
this.embedInit.isInitialized$.subscribe((ready) => {
this.isInitialized = ready;
});
}
}🎨 Responsive Behavior
Fixed Positioning
- Desktop (> 500px): Widget stays at specified position
- Mobile (≤ 500px): Widget expands to full width with 1rem padding from edges
Embedded Positioning
- Wide parent (> 400px): Widget aligns to left/right based on
sideinput - Narrow parent (≤ 400px): Widget auto-centers with equal padding
🔧 API Reference
EmbedButtonComponent Inputs
Selector: revrag-embed-button
interface EmbedButtonInputs {
// Positioning mode
positioning?: 'fixed' | 'embedded'; // default: 'fixed'
// Position configuration (for fixed mode)
position?: {
top?: string;
bottom?: string;
left?: string;
right?: string;
zIndex?: number;
};
// Easy positioning (for embedded mode)
side?: 'left' | 'right' | 'center';
// Offset from bottom (useful for bottom navbars)
bottomOffset?: number; // in pixels
// Custom CSS class
className?: string;
}EmbedProviderComponent Inputs
Selector: revrag-embed-provider
Use this component to declaratively control widget visibility based on the current route.
interface EmbedProviderInputs {
// Active URL path — keep in sync with router
currentPath?: string;
// Paths where the widget should be visible
includeScreens?: string[];
// How paths are matched
matchMode?: 'exact' | 'startsWith'; // default: 'exact'
// Delay before button appears (ms)
embedButtonDelayMs?: number;
// Advanced group-based visibility rules
embedButtonVisibilityConfig?: EmbedButtonVisibilityConfig;
// Override button position
embedButtonPosition?: EmbedButtonPosition;
// Widget positioning mode
widgetPositioning?: 'fixed' | 'embedded'; // default: 'fixed'
// Horizontal alignment
widgetSide?: 'left' | 'right';
// Extra pixels from bottom edge
widgetBottomOffset?: number;
// Extra CSS class on widget container
widgetClassName?: string;
}Advanced usage guide →
EmbedInitService
import { EmbedInitService } from '@revrag-ai/embed-angular';
constructor(private embedInit: EmbedInitService) {}
// Initialize the SDK (call once in AppComponent)
await this.embedInit.initialize(apiKey: string, options?: {
baseUrl?: string; // Override API base URL
enableTracker?: boolean; // Enable automatic tracking
trackerCallback?: (event: EventPayload) => void;
});
// Observables
this.embedInit.isInitialized$ // Observable<boolean>
this.embedInit.isLoading$ // Observable<boolean>
this.embedInit.error$ // Observable<string | null>
this.embedInit.sessionData$ // Observable<SessionStorageData>
// Synchronous getter
this.embedInit.isInitialized // boolean📡 Event Management
The SDK provides a powerful event system for tracking user data, custom events, and listening to agent state changes. Use the embedEvent singleton (re-exported from @revrag-ai/embed-angular) — it has the same API as embed in the React SDK.
EventKeys
Available event types:
import { EventKeys } from '@revrag-ai/embed-angular';
EventKeys.USER_DATA // 'user_data' - User identification and profile data
EventKeys.CUSTOM_EVENT // 'custom_event' - Custom application events
EventKeys.AGENT_CONNECTED // 'agent_start' - Voice agent connection (auto-tracked)
EventKeys.AGENT_DISCONNECTED // 'agent_end' - Voice agent disconnection (auto-tracked)
EventKeys.ANALYTICS_DATA // 'analytics_data'Note: Only USER_DATA, CUSTOM_EVENT and ANALYTICS_DATA are available for manual use. Agent connection events are automatically tracked by the SDK and can be listened to via callbacks.
Sending Events with embedEvent API
The embedEvent object provides methods for sending events to track user data and custom application events.
Send User Data
import { embedEvent, EventKeys } from '@revrag-ai/embed-angular';
// Send user data
const response = await embedEvent.event({
eventKey: EventKeys.USER_DATA,
data: {
app_user_id: 'user-123',
email: 'user@example.com',
name: 'John Doe',
plan: 'premium'
}
});
if (response.success) {
console.log('User data sent successfully');
}Send Custom Events
import { embedEvent, EventKeys } from '@revrag-ai/embed-angular';
// Track custom application event
await embedEvent.event({
eventKey: EventKeys.CUSTOM_EVENT,
data: {
event_name: 'purchase_completed',
product_id: 'prod-123',
amount: 99.99
}
});Send Analytics Events
import { embedEvent, EventKeys } from '@revrag-ai/embed-angular';
// Track analytics event
await embedEvent.event({
eventKey: EventKeys.ANALYTICS_DATA,
data: {
event_name: 'purchase_completed', // event_name is compulsory in analytics_data event
product_id: 'prod-123',
amount: 99.99
}
});Event Method Signature
embedEvent.event(params: UpdateDataRequest): Promise<ApiResponse>
interface UpdateDataRequest {
eventKey: EventKey; // Event type from EventKeys
data: {
app_user_id?: string; // User ID (required for USER_DATA)
[key: string]: unknown; // Additional data
};
session_id?: string; // Optional session ID
}
interface ApiResponse {
success: boolean;
data?: unknown;
message?: string;
error?: string;
}Listening to Agent Events
Monitor voice agent connection status in real-time. These events are automatically sent to your backend AND emitted locally for you to listen to.
Available Event Types for Listening
import { EventKeys } from '@revrag-ai/embed-angular';
// Available events for listening:
EventKeys.AGENT_CONNECTED // 'agent_start' - Voice agent connected
EventKeys.AGENT_DISCONNECTED // 'agent_end' - Voice agent disconnectedAutomatic Backend Sync:
- Agent events are automatically sent to your backend with
app_user_id - Events are also emitted locally for real-time UI updates
- Backend receives all event data including timestamps and metadata
- No manual API calls needed — it's all handled automatically
Event Listener Methods
import { embedEvent } from '@revrag-ai/embed-angular';
// Add event listener
embedEvent.addCallback(callback);
// Remove event listener
embedEvent.removeCallback(callback);Basic Event Listening Example
import { Component, OnInit, OnDestroy } from '@angular/core';
import { embedEvent, EventKeys, EmbedButtonComponent } from '@revrag-ai/embed-angular';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-my-component',
standalone: true,
imports: [CommonModule, EmbedButtonComponent],
template: `<revrag-embed-button></revrag-embed-button>`,
})
export class MyComponent implements OnInit, OnDestroy {
private handleAgentEvent = (event: any) => {
if (event.type === EventKeys.AGENT_CONNECTED) {
console.log('✅ Agent connected:', event.data);
// Update UI to show agent is available
}
if (event.type === EventKeys.AGENT_DISCONNECTED) {
console.log('❌ Agent disconnected:', event.data);
// Update UI to show agent is unavailable
}
};
ngOnInit(): void {
embedEvent.addCallback(this.handleAgentEvent);
}
ngOnDestroy(): void {
embedEvent.removeCallback(this.handleAgentEvent);
}
}Complete Agent Monitoring Example
import { Component, OnInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { embedEvent, EventKeys, EmbedButtonComponent } from '@revrag-ai/embed-angular';
@Component({
selector: 'app-voice-agent-monitor',
standalone: true,
imports: [CommonModule, EmbedButtonComponent],
template: `
<div class="agent-monitor">
<div class="status-indicator">
<span class="status-dot" [class]="agentStatus"></span>
<span>Agent Status: {{ agentStatus }}</span>
</div>
<div class="agent-info" *ngIf="agentStatus === 'connected'">
<p>✓ Voice agent is active</p>
<p>Identity: {{ agentIdentity }}</p>
<p *ngIf="connectionTime">Connected at: {{ connectionTime | date:'mediumTime' }}</p>
</div>
<revrag-embed-button></revrag-embed-button>
</div>
`,
})
export class VoiceAgentMonitorComponent implements OnInit, OnDestroy {
agentStatus: 'idle' | 'connected' | 'disconnected' = 'idle';
agentIdentity = '';
connectionTime: Date | null = null;
private handleEvent = (event: any) => {
if (event.type === EventKeys.AGENT_CONNECTED) {
console.log('✅ Agent connected:', event);
console.log('Identity:', event.data?.identity);
console.log('Metadata:', event.data?.metadata);
console.log('Timestamp:', event.timestamp);
this.agentStatus = 'connected';
this.agentIdentity = event.data?.identity || 'Unknown';
this.connectionTime = new Date(event.timestamp);
}
if (event.type === EventKeys.AGENT_DISCONNECTED) {
console.log('❌ Agent disconnected:', event);
this.agentStatus = 'disconnected';
if (this.connectionTime) {
const duration = Date.now() - this.connectionTime.getTime();
console.log('Call duration:', duration / 1000, 'seconds');
}
}
};
ngOnInit(): void {
embedEvent.addCallback(this.handleEvent);
}
ngOnDestroy(): void {
embedEvent.removeCallback(this.handleEvent);
}
}Use Cases for Agent Events
AGENT_CONNECTED:
- Show visual indicators (green dot, badge)
- Enable voice-related features in UI
- Start analytics timers
- Update user presence status
- Show notifications to user
- Pause background music/media
AGENT_DISCONNECTED:
- Update UI to show agent unavailable
- Log analytics (call duration, success)
- Show feedback forms
- Resume background media
- Clean up resources
- Save conversation state
Handling Connection Errors
import { Component, OnInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { embedEvent, EventKeys, EmbedButtonComponent } from '@revrag-ai/embed-angular';
@Component({
selector: 'app-agent-with-error-handling',
standalone: true,
imports: [CommonModule, EmbedButtonComponent],
template: `
<div>
<div class="error-banner" *ngIf="error">{{ error }}</div>
<revrag-embed-button></revrag-embed-button>
</div>
`,
})
export class AgentWithErrorHandlingComponent implements OnInit, OnDestroy {
error: string | null = null;
private handleEvent = (event: any) => {
try {
if (event.type === EventKeys.AGENT_CONNECTED) {
this.error = null;
}
if (event.type === EventKeys.AGENT_DISCONNECTED) {
if (event.data?.metadata?.error) {
this.error = 'Agent connection lost: ' + event.data.metadata.error;
}
}
} catch (err) {
console.error('Error handling agent event:', err);
this.error = 'Failed to process agent event';
}
};
ngOnInit(): void {
embedEvent.addCallback(this.handleEvent);
}
ngOnDestroy(): void {
embedEvent.removeCallback(this.handleEvent);
}
}📱 Mobile Optimization
The widget automatically adjusts for mobile devices:
Extra Small Screens (≤ 375px)
- iPhone SE, small Android devices
- Reduced padding and font sizes
- Optimized button and text layouts
Small Screens (376px - 500px)
- Standard smartphones
- Balanced sizing for readability
🎯 Common Use Cases
1. Customer Support Widget with User Tracking
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import {
EmbedButtonComponent,
EmbedInitService,
embedEvent,
EventKeys,
} from '@revrag-ai/embed-angular';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, RouterModule, EmbedButtonComponent],
template: `
<router-outlet></router-outlet>
<revrag-embed-button
*ngIf="isInitialized"
positioning="fixed"
[position]="{ bottom: '24px', right: '24px' }"
></revrag-embed-button>
`,
})
export class AppComponent implements OnInit {
isInitialized = false;
constructor(private embedInit: EmbedInitService) {}
ngOnInit(): void {
this.embedInit.initialize('your-api-key').then(() => {
this.isInitialized = true;
this.sendUserData();
});
}
private async sendUserData(): Promise<void> {
const currentUser = this.getCurrentUser(); // your auth method
if (currentUser) {
await embedEvent.event({
eventKey: EventKeys.USER_DATA,
data: {
app_user_id: currentUser.id,
email: currentUser.email,
name: currentUser.name,
subscription_tier: currentUser.plan,
},
});
}
}
private getCurrentUser() {
// replace with your auth service call
return null;
}
}2. E-commerce with Purchase Tracking
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
EmbedButtonComponent,
EmbedInitService,
embedEvent,
EventKeys,
} from '@revrag-ai/embed-angular';
@Component({
selector: 'app-checkout',
standalone: true,
imports: [CommonModule, EmbedButtonComponent],
template: `
<div>
<h2>Checkout</h2>
<button (click)="handleCheckout()">Complete Order</button>
<revrag-embed-button
*ngIf="isInitialized"
positioning="embedded"
side="right"
></revrag-embed-button>
</div>
`,
})
export class CheckoutPageComponent implements OnInit {
isInitialized = false;
constructor(private embedInit: EmbedInitService) {}
ngOnInit(): void {
this.embedInit.isInitialized$.subscribe((ready) => {
this.isInitialized = ready;
});
}
async handleCheckout(): Promise<void> {
// ... process order
await embedEvent.event({
eventKey: EventKeys.CUSTOM_EVENT,
data: {
event_name: 'purchase_completed',
order_id: 'order-456',
total: 99.99,
items: 3,
},
});
}
}3. Help Section Widget with Agent Status
import { Component, OnInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
EmbedButtonComponent,
EmbedInitService,
embedEvent,
EventKeys,
} from '@revrag-ai/embed-angular';
@Component({
selector: 'app-help-page',
standalone: true,
imports: [CommonModule, EmbedButtonComponent],
template: `
<div class="help-container">
<h1>Help & Support</h1>
<div class="status-badge" *ngIf="agentConnected">🟢 Agent Connected</div>
<div style="position: relative; min-height: 400px">
<revrag-embed-button
*ngIf="isInitialized"
positioning="embedded"
side="right"
></revrag-embed-button>
</div>
</div>
`,
})
export class HelpPageComponent implements OnInit, OnDestroy {
isInitialized = false;
agentConnected = false;
private handleAgentEvent = (event: any) => {
if (event.type === EventKeys.AGENT_CONNECTED) {
this.agentConnected = true;
}
if (event.type === EventKeys.AGENT_DISCONNECTED) {
this.agentConnected = false;
}
};
constructor(private embedInit: EmbedInitService) {}
ngOnInit(): void {
this.embedInit.isInitialized$.subscribe((ready) => {
this.isInitialized = ready;
});
embedEvent.addCallback(this.handleAgentEvent);
}
ngOnDestroy(): void {
embedEvent.removeCallback(this.handleAgentEvent);
}
}4. With Bottom Navigation
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { EmbedButtonComponent, EmbedInitService } from '@revrag-ai/embed-angular';
@Component({
selector: 'app-mobile-shell',
standalone: true,
imports: [CommonModule, EmbedButtonComponent],
template: `
<div class="content">
<!-- Your main content -->
</div>
<nav class="bottom-nav"><!-- height: 60px --></nav>
<revrag-embed-button
*ngIf="isInitialized"
positioning="fixed"
[bottomOffset]="60"
></revrag-embed-button>
`,
})
export class MobileShellComponent implements OnInit {
isInitialized = false;
constructor(private embedInit: EmbedInitService) {}
ngOnInit(): void {
this.embedInit.isInitialized$.subscribe((ready) => {
this.isInitialized = ready;
});
}
}5. Multi-Department Support
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { EmbedButtonComponent, EmbedInitService } from '@revrag-ai/embed-angular';
@Component({
selector: 'app-support-page',
standalone: true,
imports: [CommonModule, EmbedButtonComponent],
template: `
<div class="support-grid" *ngIf="isInitialized">
<div class="support-card">
<h3>Sales Support</h3>
<p>Questions about pricing and plans</p>
<div style="position: relative; height: 400px">
<revrag-embed-button positioning="embedded" side="left"></revrag-embed-button>
</div>
</div>
<div class="support-card">
<h3>Technical Support</h3>
<p>Help with technical issues</p>
<div style="position: relative; height: 400px">
<revrag-embed-button positioning="embedded" side="right"></revrag-embed-button>
</div>
</div>
</div>
`,
})
export class SupportPageComponent implements OnInit {
isInitialized = false;
constructor(private embedInit: EmbedInitService) {}
ngOnInit(): void {
this.embedInit.isInitialized$.subscribe((ready) => {
this.isInitialized = ready;
});
}
}6. Contextual Events Based on User Actions
import { Component, Input, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
EmbedButtonComponent,
EmbedInitService,
embedEvent,
EventKeys,
} from '@revrag-ai/embed-angular';
@Component({
selector: 'app-product-page',
standalone: true,
imports: [CommonModule, EmbedButtonComponent],
template: `
<div>
<h2>{{ product.name }}</h2>
<p>{{ product.price | currency }}</p>
<button (click)="handleAddToCart()">Add to Cart</button>
<revrag-embed-button *ngIf="isInitialized"></revrag-embed-button>
</div>
`,
})
export class ProductPageComponent implements OnInit {
@Input() product: { id: string; name: string; price: number } = { id: '', name: '', price: 0 };
isInitialized = false;
constructor(private embedInit: EmbedInitService) {}
ngOnInit(): void {
this.embedInit.isInitialized$.subscribe((ready) => {
this.isInitialized = ready;
});
}
async handleAddToCart(): Promise<void> {
await embedEvent.event({
eventKey: EventKeys.CUSTOM_EVENT,
data: {
event_name: 'product_added_to_cart',
product_id: this.product.id,
product_name: this.product.name,
price: this.product.price,
},
});
}
}🔄 Complete Integration Example
Here's a complete example showing initialization, user tracking, event listening, and the widget all working together:
// app.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import {
EmbedButtonComponent,
EmbedInitService,
embedEvent,
EventKeys,
} from '@revrag-ai/embed-angular';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, RouterModule, EmbedButtonComponent],
template: `
<div class="app">
<header>
<h1>My Application</h1>
<div class="status">
Agent Status: <span [class]="agentStatus">{{ agentStatus }}</span>
</div>
</header>
<main>
<router-outlet></router-outlet>
<button (click)="handlePurchase()">Complete Purchase</button>
</main>
<!-- AI Widget — will appear at bottom-right -->
<revrag-embed-button *ngIf="isInitialized && userDataSent"></revrag-embed-button>
</div>
`,
})
export class AppComponent implements OnInit, OnDestroy {
isInitialized = false;
isLoading = false;
error: string | null = null;
agentStatus: 'idle' | 'connected' | 'disconnected' = 'idle';
userDataSent = false;
private handleAgentEvent = (event: any) => {
if (event.type === EventKeys.AGENT_CONNECTED) {
console.log('Agent connected:', event);
this.agentStatus = 'connected';
}
if (event.type === EventKeys.AGENT_DISCONNECTED) {
console.log('Agent disconnected:', event);
this.agentStatus = 'disconnected';
}
};
constructor(private embedInit: EmbedInitService) {}
ngOnInit(): void {
// 1. Subscribe to loading and error states
this.embedInit.isLoading$.subscribe((loading) => {
this.isLoading = loading;
});
this.embedInit.error$.subscribe((err) => {
this.error = err;
});
// 2. Initialize SDK
this.embedInit.initialize('your-api-key').then(() => {
this.isInitialized = true;
this.initializeUserData();
});
// 3. Listen to agent events
embedEvent.addCallback(this.handleAgentEvent);
}
ngOnDestroy(): void {
embedEvent.removeCallback(this.handleAgentEvent);
}
private async initializeUserData(): Promise<void> {
try {
await embedEvent.event({
eventKey: EventKeys.USER_DATA,
data: {
app_user_id: 'user-123',
email: 'user@example.com',
name: 'John Doe',
subscription: 'premium',
},
});
this.userDataSent = true;
} catch (err) {
console.error('Failed to initialize user data:', err);
}
}
async handlePurchase(): Promise<void> {
await embedEvent.event({
eventKey: EventKeys.CUSTOM_EVENT,
data: {
event_name: 'purchase_completed',
amount: 99.99,
product_id: 'prod-123',
},
});
}
}Key Points in This Example:
- ✅ CSS Import: Added to
angular.jsonstyles array - ✅ Animations:
provideAnimations()inapp.config.ts - ✅ SDK Initialization:
EmbedInitService.initialize()inngOnInit()with loading/error states - ✅ User Data: Sent first before rendering the widget
- ✅ Event Listeners: Registered in
ngOnInit(), cleaned up inngOnDestroy() - ✅ Custom Events: Tracked when user performs actions
- ✅ Widget Rendering: Only rendered after successful initialization and user data sent
⚡ Angular-Specific Notes
Standalone Components (Recommended, Angular 15+)
Import EmbedButtonComponent or EmbedProviderComponent directly into each component's imports array:
@Component({
standalone: true,
imports: [CommonModule, EmbedButtonComponent],
// ...
})
export class MyComponent {}NgModule-based Apps
For applications that have not migrated to standalone components, import EmbedModule once in your root or feature module:
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { EmbedModule } from '@revrag-ai/embed-angular';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
BrowserAnimationsModule, // required
EmbedModule, // all selectors available everywhere
],
bootstrap: [AppComponent],
})
export class AppModule {}All component selectors and inputs remain the same. You do not need to import individual components when EmbedModule is imported.
Server-Side Rendering (SSR / Angular Universal)
The SDK uses PlatformService internally to detect the browser platform and skips all DOM operations during SSR. No additional configuration is needed — the widget renders nothing on the server and hydrates cleanly on the client.
Tailwind CSS Projects
✅ No conflicts! The library uses custom embed-* prefixed classes
✅ Your Tailwind styles won't affect the widget
✅ Widget styles won't affect your app
🐛 Troubleshooting
Widget appears unstyled
Solution: Make sure the CSS path is in your angular.json styles array:
"styles": [
"src/styles.css",
"node_modules/@revrag-ai/embed-angular/styles/widget.css"
]Rebuild the app after making this change.
Widget not appearing
Solution: Ensure EmbedInitService.initialize() has completed before rendering the widget:
this.embedInit.isInitialized$.subscribe((ready) => {
this.isInitialized = ready;
});Check the browser console for initialization errors — your API key may be invalid.
Widget overlaps with bottom navigation
Solution: Use the bottomOffset input:
<revrag-embed-button positioning="fixed" [bottomOffset]="60"></revrag-embed-button>Animations are broken or missing
Solution: Ensure provideAnimations() (standalone) or BrowserAnimationsModule (NgModule) is present in your app config.
Events not being sent
Solution: Ensure you've sent USER_DATA event first with app_user_id:
await embedEvent.event({
eventKey: EventKeys.USER_DATA,
data: {
app_user_id: 'user-123',
// ... other data
}
});Agent event listeners not firing
Solution: Make sure callbacks are registered in ngOnInit() before the agent connects, and cleaned up in ngOnDestroy():
ngOnInit(): void {
embedEvent.addCallback(this.handleEvent);
}
ngOnDestroy(): void {
embedEvent.removeCallback(this.handleEvent);
}"User identity not found" error
Solution: Send USER_DATA event before any other events:
// ✅ Correct order
await embedEvent.event({ eventKey: EventKeys.USER_DATA, data: { app_user_id: 'user-123' } });
await embedEvent.event({ eventKey: EventKeys.CUSTOM_EVENT, data: { ... } });
// ❌ Wrong order
await embedEvent.event({ eventKey: EventKeys.CUSTOM_EVENT, data: { ... } }); // Error!Custom events being blocked
Solution: Only USER_DATA and CUSTOM_EVENT are allowed for manual sending. Agent events (AGENT_CONNECTED, AGENT_DISCONNECTED) are auto-tracked and can only be listened to, not manually sent.
Analytics event missing or rejected
Solution: event_name is a required field in every ANALYTICS_DATA event. Omitting it will cause the event to be dropped silently.
// ❌ Missing event_name — event will be rejected
await embedEvent.event({
eventKey: EventKeys.ANALYTICS_DATA,
data: {
product_id: 'prod-456',
value: 299,
}
});
// ✅ Correct — event_name is always required
await embedEvent.event({
eventKey: EventKeys.ANALYTICS_DATA,
data: {
event_name: 'purchase_completed', // required
product_id: 'prod-456',
value: 299,
}
});Widget appears behind other elements
Solution: Use the position input to set a custom zIndex:
<revrag-embed-button
[position]="{ bottom: '24px', right: '24px', zIndex: 99999 }"
></revrag-embed-button>📋 Checklist
Before deploying, ensure:
Basic Setup:
- CSS path is in
angular.jsonstyles array -
provideAnimations()orBrowserAnimationsModuleis configured - API key is configured
-
isInitialized$is checked before rendering the widget - Parent container has
position: relative(for embedded mode) - Parent container has sufficient height (for embedded mode)
- Bottom offset is set if you have bottom navigation
Event System:
-
USER_DATAevent sent first withapp_user_id -
USER_DATAsent before renderingrevrag-embed-button - Event listeners registered in
ngOnInit()before agent connection - Event listeners cleaned up in
ngOnDestroy() - Custom events include proper context (screen, flow)
Production Readiness:
- Error handling for failed event sends
- Loading states during SDK initialization (
isLoading$) - Agent connection status displayed to users
- Analytics tracking for agent events
- Proper cleanup of callbacks on component destroy
🆘 Support
- 📧 Issues: GitHub Issues
- 📖 Docs: GitHub README
- 💬 Discussions: GitHub Discussions
🎉 You're All Set!
The widget is now ready to use. It's:
- ✅ Fully responsive
- ✅ Angular-native (standalone + NgModule)
- ✅ Tailwind-compatible
- ✅ Production-ready
- ✅ Mobile-optimized
- ✅ Real-time event tracking
- ✅ Voice agent monitoring
- ✅ User context aware
- ✅ SSR-safe (Angular Universal)
Quick Reference
Import everything you need:
import {
EmbedButtonComponent, // The main widget component selector: revrag-embed-button
EmbedProviderComponent, // Route-aware wrapper selector: revrag-embed-provider
EmbedInitService, // SDK initialization service
embedEvent, // Event management API
EventKeys, // Event type constants
EmbedModule, // NgModule bundle (NgModule apps only)
} from '@revrag-ai/embed-angular';Initialize and track:
// 1. Initialize SDK (in AppComponent.ngOnInit)
await this.embedInit.initialize('your-api-key');
// 2. Send user data
await embedEvent.event({
eventKey: EventKeys.USER_DATA,
data: { app_user_id: 'user-123' }
});
// 3. Listen to agent events (register in ngOnInit, remove in ngOnDestroy)
embedEvent.addCallback((event) => {
if (event.type === EventKeys.AGENT_CONNECTED) {
console.log('Agent connected!');
}
});
// 4. Render widget
// <revrag-embed-button></revrag-embed-button>Happy coding! 🚀
Built with Mintlify.