Skip to main content

πŸš€ Complete Integration Guide

πŸ“¦ Installation

npm install @revrag-ai/embed-angular livekit-client lottie-web
# or
yarn add @revrag-ai/embed-angular livekit-client lottie-web

Peer Dependencies

Ensure the following Angular packages are present in your project (they are usually already installed):
PackageVersion
@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. 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 side input
  • 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 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

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:

  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

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.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


πŸŽ‰ 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.