# 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: `
`,
})
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: `
`,
})
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: `
`,
})
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).