# Angular

> Angular SDK Integration Guide - Voice-enabled AI agent with real-time communication capabilities

URL: /embed/integration/angular
Markdown: /embed/integration/angular.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: `<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](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: `
    <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 [#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: `
    <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 [#-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;
}
```

<a href="/embed/integration/embed-angular-provider-advanced" target="_blank" rel="noreferrer">
  Advanced usage guide →
</a>

### 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<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 [#-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<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 [#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: `<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 [#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: `
    <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 [#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: `
    <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 [#-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: `
    <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 [#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: `
    <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 [#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: `
    <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 [#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: `
    <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 [#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: `
    <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 [#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: `
    <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 [#-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: `
    <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: [#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
<revrag-embed-button positioning="fixed" [bottomOffset]="60"></revrag-embed-button>
```

### 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
<revrag-embed-button
  [position]="{ bottom: '24px', right: '24px', zIndex: 99999 }"
></revrag-embed-button>
```

***

## 📋 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
// <revrag-embed-button></revrag-embed-button>
```

Happy coding! 🚀

Built with [Mintlify](https://mintlify.com).
