# Angular (1.0.7) > Angular SDK Integration Guide - Voice-enabled AI agent with real-time communication capabilities URL: /embed/integration/angular/v1.0.7 Markdown: /embed/integration/angular/v1.0.7.md # 🚀 Complete Integration Guide [#-complete-integration-guide] ## 📦 Installation [#-installation] ```bash npm install @revrag-ai/embed-angular livekit-client lottie-web # or yarn add @revrag-ai/embed-angular livekit-client lottie-web ``` ### Peer Dependencies [#peer-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) [#️-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) [#option-1-angularjson--projectjson-recommended] Add the path to the `styles` array in your `angular.json` (or `project.json` for Nx workspaces): ```json { "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 [#option-2-global-stylescss-import] ```css /* In your global styles.css */ @import '@revrag-ai/embed-angular/styles/widget.css'; ``` ### Option 3: Component-Level Import [#option-3-component-level-import] ```css /* In a component's .css / .scss file */ @import '@revrag-ai/embed-angular/styles/widget.css'; ``` *** ## ⚡ Setup [#-setup] Three one-time steps are required before placing any widget component. ### Step 1 — Provide Animations [#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`):** ```typescript 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`):** ```typescript 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 [#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. ```typescript // 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: ``, }) 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](https://revrag.ai). *** ## 🎯 Basic Usage [#-basic-usage] ### Fixed Positioning (Default) [#fixed-positioning-default] ```typescript 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: `

My Application

`, }) export class MyPageComponent implements OnInit { isInitialized = false; constructor(private embedInit: EmbedInitService) {} ngOnInit(): void { this.embedInit.isInitialized$.subscribe((ready) => { this.isInitialized = ready; }); } } ``` ### Embedded Positioning [#embedded-positioning] ```typescript 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: `

Need Help?

Chat with our AI assistant

`, }) export class HelpSectionComponent implements OnInit { isInitialized = false; constructor(private embedInit: EmbedInitService) {} ngOnInit(): void { this.embedInit.isInitialized$.subscribe((ready) => { this.isInitialized = ready; }); } } ``` *** ## 🎨 Responsive Behavior [#-responsive-behavior] ### Fixed Positioning [#fixed-positioning] * **Desktop (> 500px)**: Widget stays at specified position * **Mobile (≤ 500px)**: Widget expands to full width with 1rem padding from edges ### Embedded Positioning [#embedded-positioning-1] * **Wide parent (> 400px)**: Widget aligns to left/right based on `side` input * **Narrow parent (≤ 400px)**: Widget auto-centers with equal padding *** ## 🔧 API Reference [#-api-reference] ### EmbedButtonComponent Inputs [#embedbuttoncomponent-inputs] **Selector:** `revrag-embed-button` ```typescript 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 [#embedprovidercomponent-inputs] **Selector:** `revrag-embed-provider` Use this component to declaratively control widget visibility based on the current route. ```typescript 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 [#embedinitservice] ```typescript 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 this.embedInit.isLoading$ // Observable this.embedInit.error$ // Observable this.embedInit.sessionData$ // Observable // Synchronous getter this.embedInit.isInitialized // boolean ``` *** ## 📡 Event Management [#-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 [#eventkeys] Available event types: ```typescript 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 [#sending-events-with-embedevent-api] The `embedEvent` object provides methods for sending events to track user data and custom application events. #### Send User Data [#send-user-data] ```typescript 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 [#send-custom-events] ```typescript 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 [#send-analytics-events] ```typescript 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 [#event-method-signature] ```typescript embedEvent.event(params: UpdateDataRequest): Promise 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 [#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 [#available-event-types-for-listening] ```typescript 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 disconnected ``` **Automatic 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 [#event-listener-methods] ```typescript import { embedEvent } from '@revrag-ai/embed-angular'; // Add event listener embedEvent.addCallback(callback); // Remove event listener embedEvent.removeCallback(callback); ``` #### Basic Event Listening Example [#basic-event-listening-example] ```typescript 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: ``, }) 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 [#complete-agent-monitoring-example] ```typescript 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: `
Agent Status: {{ agentStatus }}

✓ Voice agent is active

Identity: {{ agentIdentity }}

Connected at: {{ connectionTime | date:'mediumTime' }}

`, }) 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 [#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 [#handling-connection-errors] ```typescript 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: `
{{ error }}
`, }) 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 [#-mobile-optimization] The widget automatically adjusts for mobile devices: ### Extra Small Screens (≤ 375px) [#extra-small-screens--375px] * iPhone SE, small Android devices * Reduced padding and font sizes * Optimized button and text layouts ### Small Screens (376px - 500px) [#small-screens-376px---500px] * Standard smartphones * Balanced sizing for readability *** ## 🎯 Common Use Cases [#-common-use-cases] ### 1. Customer Support Widget with User Tracking [#1-customer-support-widget-with-user-tracking] ```typescript 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: ` `, }) 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 { 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 [#2-e-commerce-with-purchase-tracking] ```typescript 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: `

Checkout

`, }) export class CheckoutPageComponent implements OnInit { isInitialized = false; constructor(private embedInit: EmbedInitService) {} ngOnInit(): void { this.embedInit.isInitialized$.subscribe((ready) => { this.isInitialized = ready; }); } async handleCheckout(): Promise { // ... 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 [#3-help-section-widget-with-agent-status] ```typescript 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: `

Help & Support

🟢 Agent Connected
`, }) 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 [#4-with-bottom-navigation] ```typescript 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: `
`, }) 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 [#5-multi-department-support] ```typescript 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: `

Sales Support

Questions about pricing and plans

Technical Support

Help with technical issues

`, }) 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 [#6-contextual-events-based-on-user-actions] ```typescript 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: `

{{ product.name }}

{{ product.price | currency }}

`, }) 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 { 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 [#-complete-integration-example] Here's a complete example showing initialization, user tracking, event listening, and the widget all working together: ```typescript // 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: `

My Application

Agent Status: {{ agentStatus }}
`, }) 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 { 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 { await embedEvent.event({ eventKey: EventKeys.CUSTOM_EVENT, data: { event_name: 'purchase_completed', amount: 99.99, product_id: 'prod-123', }, }); } } ``` ### Key Points in This Example: [#key-points-in-this-example] 1. **✅ CSS Import**: Added to `angular.json` styles array 2. **✅ Animations**: `provideAnimations()` in `app.config.ts` 3. **✅ SDK Initialization**: `EmbedInitService.initialize()` in `ngOnInit()` with loading/error states 4. **✅ User Data**: Sent first before rendering the widget 5. **✅ Event Listeners**: Registered in `ngOnInit()`, cleaned up in `ngOnDestroy()` 6. **✅ Custom Events**: Tracked when user performs actions 7. **✅ Widget Rendering**: Only rendered after successful initialization and user data sent *** ## ⚡ Angular-Specific Notes [#-angular-specific-notes] ### Standalone Components (Recommended, Angular 15+) [#standalone-components-recommended-angular-15] Import `EmbedButtonComponent` or `EmbedProviderComponent` directly into each component's `imports` array: ```typescript @Component({ standalone: true, imports: [CommonModule, EmbedButtonComponent], // ... }) export class MyComponent {} ``` ### NgModule-based Apps [#ngmodule-based-apps] For applications that have not migrated to standalone components, import `EmbedModule` once in your root or feature module: ```typescript // 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) [#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 [#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 [#-troubleshooting] ### Widget appears unstyled [#widget-appears-unstyled] **Solution**: Make sure the CSS path is in your `angular.json` styles array: ```json "styles": [ "src/styles.css", "node_modules/@revrag-ai/embed-angular/styles/widget.css" ] ``` Rebuild the app after making this change. ### Widget not appearing [#widget-not-appearing] **Solution**: Ensure `EmbedInitService.initialize()` has completed before rendering the widget: ```typescript 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 [#widget-overlaps-with-bottom-navigation] **Solution**: Use the `bottomOffset` input: ```html ``` ### Animations are broken or missing [#animations-are-broken-or-missing] **Solution**: Ensure `provideAnimations()` (standalone) or `BrowserAnimationsModule` (NgModule) is present in your app config. ### Events not being sent [#events-not-being-sent] **Solution**: Ensure you've sent `USER_DATA` event first with `app_user_id`: ```typescript await embedEvent.event({ eventKey: EventKeys.USER_DATA, data: { app_user_id: 'user-123', // ... other data } }); ``` ### Agent event listeners not firing [#agent-event-listeners-not-firing] **Solution**: Make sure callbacks are registered in `ngOnInit()` before the agent connects, and cleaned up in `ngOnDestroy()`: ```typescript ngOnInit(): void { embedEvent.addCallback(this.handleEvent); } ngOnDestroy(): void { embedEvent.removeCallback(this.handleEvent); } ``` ### "User identity not found" error [#user-identity-not-found-error] **Solution**: Send `USER_DATA` event before any other events: ```typescript // ✅ 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 [#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 [#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. ```typescript // ❌ 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 [#widget-appears-behind-other-elements] **Solution**: Use the `position` input to set a custom `zIndex`: ```html ``` *** ## 📋 Checklist [#-checklist] Before deploying, ensure: **Basic Setup:** * [ ] CSS path is in `angular.json` styles array * [ ] `provideAnimations()` or `BrowserAnimationsModule` is 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_DATA` event sent first with `app_user_id` * [ ] `USER_DATA` sent before rendering `revrag-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 [#-support] * 📧 Issues: [GitHub Issues](https://github.com/revrag-ai/embed-react/issues) * 📖 Docs: [GitHub README](https://github.com/revrag-ai/embed-react) * 💬 Discussions: [GitHub Discussions](https://github.com/revrag-ai/embed-react/discussions) *** ## 🎉 You're All Set! [#-youre-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 [#quick-reference] **Import everything you need:** ```typescript 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:** ```typescript // 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 // ``` Happy coding! 🚀 Built with [Mintlify](https://mintlify.com).