npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@revrag-ai/embed-angular

v1.0.6

Published

Angular SDK for the Revrag AI assistant widget

Downloads

309

Readme

@revrag-ai/embed-angular

Angular SDK for the Revrag AI assistant widget. Add a fully-featured AI voice + chat assistant to any Angular application in minutes.

Self-contained. The core SDK logic is bundled inside this package — no separate @revrag-ai/embed-core install required.


Table of Contents


Requirements

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


Installation

npm install @revrag-ai/embed-angular livekit-client lottie-web

Setup

Three one-time steps are required before placing any widget component.

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 { BrowserAnimationsModule } from '@angular/platform-browser/animations';

@NgModule({
  imports: [BrowserModule, BrowserAnimationsModule],
})
export class AppModule {}

2. Add the stylesheet

Include the widget stylesheet in your Angular build. Open angular.json and add the path to the styles array under your project's build target:

{
  "projects": {
    "your-app": {
      "architect": {
        "build": {
          "options": {
            "styles": [
              "src/styles.css",
              "node_modules/@revrag-ai/embed-angular/styles/widget.css"
            ]
          }
        }
      }
    }
  }
}

Note: styles/widget.css is bundled inside @revrag-ai/embed-angular at publish time — no separate @revrag-ai/embed-core install is needed.


3. Initialize the SDK

Call EmbedInitService.initialize() once, as early as possible — typically in your root AppComponent. It validates your API key, fetches the 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.


Integration Patterns

Choose the pattern that best fits your application's routing strategy.


Pattern 1 — Manual conditional rendering

Best for: apps where you want explicit, imperative control over when the widget appears.

Import EmbedButtonComponent directly and use *ngIf to conditionally render it based on the current route.

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs';
import { EmbedButtonComponent } from '@revrag-ai/embed-angular';

@Component({
  selector: 'app-shell',
  standalone: true,
  imports: [CommonModule, RouterModule, EmbedButtonComponent],
  template: `
    <nav>
      <a routerLink="/">Home</a>
      <a routerLink="/offers">Offers</a>
      <a routerLink="/about">About</a>
    </nav>

    <router-outlet></router-outlet>

    <!-- Widget only on the /offers route -->
    <revrag-embed-button
      *ngIf="showEmbed"
      positioning="fixed"
      side="center"
    ></revrag-embed-button>
  `,
})
export class AppShellComponent {
  showEmbed = false;

  constructor(private router: Router) {
    this.router.events
      .pipe(filter((e): e is NavigationEnd => e instanceof NavigationEnd))
      .subscribe((e) => {
        this.showEmbed = e.urlAfterRedirects.includes('/offers');
      });
  }
}

Pattern 2 — Provider with includeScreens

Best for: router-based apps where the widget should appear on a known set of routes.

Wrap your layout with <revrag-embed-provider> and declare which paths should show the widget. The provider listens to the Angular router automatically if you keep currentPath in sync.

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs';
import { EmbedProviderComponent } from '@revrag-ai/embed-angular';

@Component({
  selector: 'app-shell',
  standalone: true,
  imports: [CommonModule, RouterModule, EmbedProviderComponent],
  template: `
    <revrag-embed-provider
      [currentPath]="currentPath"
      [includeScreens]="['/offers', '/help']"
      matchMode="exact"
      widgetPositioning="fixed"
    >
      <nav>
        <a routerLink="/">Home</a>
        <a routerLink="/offers">Offers</a>
        <a routerLink="/help">Help</a>
      </nav>

      <router-outlet></router-outlet>
    </revrag-embed-provider>
  `,
})
export class AppShellComponent {
  currentPath = '/';

  constructor(private router: Router) {
    this.router.events
      .pipe(filter((e): e is NavigationEnd => e instanceof NavigationEnd))
      .subscribe((e) => {
        this.currentPath = e.urlAfterRedirects;
      });
  }
}

Use matchMode="startsWith" to show the widget on /offers and any sub-routes like /offers/details.


Pattern 3 — Object-based config

Best for: teams that prefer keeping widget configuration in a separate object (easier to share across components or load from a config service).

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs';
import { EmbedProviderComponent } from '@revrag-ai/embed-angular';

const EMBED_CONFIG = {
  includeScreens: ['/offers', '/help', '/support'],
  matchMode: 'exact' as const,
};

@Component({
  selector: 'app-shell',
  standalone: true,
  imports: [CommonModule, RouterModule, EmbedProviderComponent],
  template: `
    <revrag-embed-provider
      [currentPath]="currentPath"
      [includeScreens]="embedConfig.includeScreens"
      [matchMode]="embedConfig.matchMode"
      widgetPositioning="fixed"
    >
      <nav>
        <a routerLink="/offers">Offers</a>
        <a routerLink="/help">Help</a>
        <a routerLink="/support">Support</a>
      </nav>

      <router-outlet></router-outlet>
    </revrag-embed-provider>
  `,
})
export class AppShellComponent {
  currentPath = '/';
  embedConfig = EMBED_CONFIG;

  constructor(private router: Router) {
    this.router.events
      .pipe(filter((e): e is NavigationEnd => e instanceof NavigationEnd))
      .subscribe((e) => {
        this.currentPath = e.urlAfterRedirects;
      });
  }
}

Pattern 4 — Tab-based navigation (no router)

Best for: single-page apps or dashboards that use tab components instead of the Angular router.

Map your active tab to a virtual path and pass it to the provider. The widget appears whenever currentPath matches a path in includeScreens.

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { EmbedProviderComponent } from '@revrag-ai/embed-angular';

interface Tab { id: string; label: string; path: string; }

const TABS: Tab[] = [
  { id: 'dashboard', label: 'Dashboard', path: '/dashboard' },
  { id: 'offers',    label: 'Offers',    path: '/offers'    },
  { id: 'settings',  label: 'Settings',  path: '/settings'  },
];

@Component({
  selector: 'app-shell',
  standalone: true,
  imports: [CommonModule, EmbedProviderComponent],
  template: `
    <revrag-embed-provider
      [currentPath]="currentPath"
      [includeScreens]="['/offers']"
      widgetPositioning="fixed"
    >
      <div class="tab-bar">
        <button
          *ngFor="let tab of tabs"
          (click)="setActiveTab(tab.id)"
          [class.active]="activeTab === tab.id"
        >
          {{ tab.label }}
        </button>
      </div>

      <div class="tab-content">
        <ng-container [ngSwitch]="activeTab">
          <div *ngSwitchCase="'dashboard'">Dashboard content</div>
          <div *ngSwitchCase="'offers'">Offers content — widget is visible here</div>
          <div *ngSwitchCase="'settings'">Settings content</div>
        </ng-container>
      </div>
    </revrag-embed-provider>
  `,
})
export class AppShellComponent {
  tabs = TABS;
  activeTab = 'dashboard';

  get currentPath(): string {
    return this.tabs.find((t) => t.id === this.activeTab)?.path ?? '/';
  }

  setActiveTab(tabId: string): void {
    this.activeTab = tabId;
  }
}

Module-based Apps (NgModule)

For applications that have not migrated to standalone components, import EmbedModule into your feature or root 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,
  ],
  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.


Component API

EmbedProviderComponent

Selector: revrag-embed-provider

The top-level declarative wrapper. Wraps your page layout via <ng-content> and controls widget visibility based on the current path. This is the recommended entry point for most apps.

| Input | Type | Default | Description | |---|---|---|---| | currentPath | string \| undefined | undefined | The active URL path. Keep this in sync with the router (or the active tab) for automatic show/hide behaviour. | | includeScreens | string[] | [] | List of paths where the widget should be visible. | | matchMode | 'exact' \| 'startsWith' | 'exact' | 'exact' — path must match exactly. 'startsWith' — path and all sub-paths match. | | embedButtonDelayMs | number | 0 | Global delay in milliseconds before the button appears after the route becomes active. | | embedButtonVisibilityConfig | EmbedButtonVisibilityConfig \| undefined | undefined | Advanced group-based delay and visibility rules. See Group-based visibility. | | embedButtonPosition | EmbedButtonPosition \| undefined | undefined | Override the button's pixel position (bottom, right). | | widgetPositioning | 'fixed' \| 'embedded' | 'fixed' | 'fixed' attaches the widget to the viewport. 'embedded' places it in normal document flow. | | widgetSide | 'left' \| 'right' \| undefined | undefined | Horizontal alignment of the widget. | | widgetBottomOffset | number | 0 | Additional pixels to raise the widget from the bottom of the screen. | | widgetClassName | string | '' | Extra CSS class applied to the widget container. |

Usage:

<revrag-embed-provider
  [currentPath]="currentPath"
  [includeScreens]="['/offers', '/help']"
  matchMode="startsWith"
  widgetPositioning="fixed"
  widgetSide="right"
  [widgetBottomOffset]="24"
>
  <!-- your page layout -->
  <router-outlet></router-outlet>
</revrag-embed-provider>

EmbedButtonComponent

Selector: revrag-embed-button

The interactive floating button. Manages the expand/collapse state, LiveKit voice connection, and tooltip display internally. Use this when you want imperative control (via *ngIf) instead of the declarative provider.

| Input | Type | Default | Description | |---|---|---|---| | positioning | 'fixed' \| 'embedded' | 'fixed' | Layout mode. | | side | 'left' \| 'right' \| 'center' \| undefined | undefined | Horizontal alignment. | | bottomOffset | number | 0 | Additional pixels from the bottom of the screen. | | className | string | '' | Extra CSS class on the button container. | | position | PositionConfig \| undefined | undefined | Fine-grained CSS position override (bottom, right, left, top). |

Usage:

<!-- Right-aligned, fixed to viewport -->
<revrag-embed-button
  positioning="fixed"
  side="right"
  [bottomOffset]="32"
></revrag-embed-button>

<!-- Centred at the bottom -->
<revrag-embed-button
  positioning="fixed"
  side="center"
></revrag-embed-button>

<!-- Custom pixel position -->
<revrag-embed-button
  [position]="{ bottom: '40px', right: '24px' }"
></revrag-embed-button>

Service API

All services are providedIn: 'root' — inject them directly without any additional provider configuration.

EmbedInitService

Handles SDK initialization and session management. Must be called once before any widget is rendered.

Injection:

import { EmbedInitService } from '@revrag-ai/embed-angular';

constructor(private embedInit: EmbedInitService) {}

initialize(apiKey, options?)

Validates the API key, fetches widget configuration, and stores it in session storage. Call this once in AppComponent.ngOnInit().

// Minimal
this.embedInit.initialize('YOUR_API_KEY');

// With options
this.embedInit.initialize('YOUR_API_KEY', {
  baseUrl: 'https://custom.api.example.com', // custom backend endpoint
  enableTracker: true,                        // enable analytics event tracking
  trackerCallback: (event) => {               // receive tracked events
    console.log('Revrag event:', event);
  },
});

| Parameter | Type | Description | |---|---|---| | apiKey | string | Your Revrag API key (required). | | options.baseUrl | string | Override the default Revrag API base URL. | | options.enableTracker | boolean | Enable automatic user interaction tracking. | | options.trackerCallback | (event: EventPayload) => void | Callback that fires for every tracked event. |

Observables:

// Subscribe to initialization state
this.embedInit.isInitialized$.subscribe((ready) => {
  console.log('SDK ready:', ready);
});

this.embedInit.isLoading$.subscribe((loading) => {
  // show a spinner while the SDK initializes
});

this.embedInit.error$.subscribe((err) => {
  if (err) console.error('SDK init failed:', err);
});

| Observable | Type | Description | |---|---|---| | isInitialized$ | Observable<boolean> | Emits true after successful initialization. | | isLoading$ | Observable<boolean> | Emits true while initialization is in progress. | | error$ | Observable<string \| null> | Emits an error message if initialization fails, otherwise null. | | sessionData$ | Observable<SessionStorageData> | Emits the full cached session data object. |

Synchronous getter:

if (this.embedInit.isInitialized) {
  // safe to interact with the widget
}

EmbedLiveKitService

Wraps the LiveKit voice connection. Use this if you need to control the voice call programmatically — for example, triggering a call from your own UI.

Injection:

import { EmbedLiveKitService } from '@revrag-ai/embed-angular';

constructor(private liveKit: EmbedLiveKitService) {}

Observables:

this.liveKit.state$.subscribe((state) => {
  console.log('Connected:', state.isConnected);
  console.log('Connecting:', state.isConnecting);
  console.log('Agent connected:', state.agentConnected);
});

this.liveKit.isMuted$.subscribe((muted) => {
  // update a mute indicator
});

this.liveKit.dataTranscription$.subscribe((data) => {
  // receive structured data pushed from the agent
});

| Observable | Type | Description | |---|---|---| | state$ | Observable<LiveKitState> | Full LiveKit connection state. | | isMuted$ | Observable<boolean> | Whether the local microphone is muted. | | dataTranscription$ | Observable<Record<string, string>> | Structured data (calculations, text) sent by the agent. | | room$ | Observable<Room> | The underlying LiveKit Room instance. |

Methods:

// Start a voice call
await this.liveKit.connect();

// End the call
this.liveKit.disconnect();

// Toggle microphone mute
await this.liveKit.toggleMute();

// Clear any received transcription data
this.liveKit.clearDataTranscription();

Advanced Configuration

Positioning

The widget can be positioned using the side shorthand or a custom position object.

Shorthand side values:

<!-- Bottom-right (default) -->
<revrag-embed-button side="right"></revrag-embed-button>

<!-- Bottom-left -->
<revrag-embed-button side="left"></revrag-embed-button>

<!-- Bottom-centre -->
<revrag-embed-button side="center"></revrag-embed-button>

Custom pixel position:

<revrag-embed-button
  [position]="{ bottom: '80px', right: '32px' }"
></revrag-embed-button>

Raise the widget above a bottom navigation bar:

<revrag-embed-provider
  [currentPath]="currentPath"
  [includeScreens]="allowedRoutes"
  [widgetBottomOffset]="64"
>
  <!-- content -->
</revrag-embed-provider>

Embedded (in-flow) mode — place the widget inside a scrollable container rather than the viewport:

<revrag-embed-button
  positioning="embedded"
  side="right"
></revrag-embed-button>

Group-based visibility

Use embedButtonVisibilityConfig on EmbedProviderComponent to define per-group delay and continuity rules. This is useful in multi-step flows where you want the widget to appear after a delay on first entry, but immediately on return visits.

import { EmbedButtonVisibilityConfig } from '@revrag-ai/embed-angular';

// In your component class:
visibilityConfig: EmbedButtonVisibilityConfig = {
  defaultDelayMs: 0,
  groups: [
    {
      id: 'checkout-flow',
      screens: ['/offers', '/checkout', '/payment'],
      continuity: 'continuous',  // remember state across these screens
      delayMs: 3000,             // wait 3s before showing on first entry
      delayPolicy: 'oncePerGroupEntry', // only delay on first entry
    },
    {
      id: 'support',
      screens: ['/help', '/support'],
      continuity: 'perScreen',   // reset state on each screen
      delayMs: 1000,
    },
  ],
};
<revrag-embed-provider
  [currentPath]="currentPath"
  [includeScreens]="allScreens"
  [embedButtonVisibilityConfig]="visibilityConfig"
>
  <router-outlet></router-outlet>
</revrag-embed-provider>

| delayPolicy | Behaviour | |---|---| | 'oncePerGroupEntry' | Delay fires only the first time the user enters this group of screens. | | 'oncePerAppSession' | Delay fires only once per browser session across all groups. | | 'perScreen' | Delay fires every time the user navigates to any screen in the group. |

| continuity | Behaviour | |---|---| | 'continuous' | Expand/collapse state is preserved while navigating within the group. | | 'perScreen' | State resets every time the user moves to a different screen. |


Type Reference

import type {
  // Config types
  SDKConfig,
  EmbedButtonVisibilityConfig,
  EmbedButtonGroupConfig,
  EmbedButtonDelayPolicy,
  EmbedButtonContinuity,
  EmbedButtonPosition,
  PositionConfig,

  // State types
  LiveKitState,
  InitResponse,
  TokenResponse,

  // Event types
  EventPayload,
  EventKeys,
} from '@revrag-ai/embed-angular';

SDKConfig

interface SDKConfig {
  apiKey: string;
  baseUrl?: string;
  enableTracker?: boolean;
  trackerCallback?: (event: EventPayload) => void;
}

PositionConfig

interface PositionConfig {
  bottom?: string;
  right?: string;
  left?: string;
  top?: string;
  transform?: string;
  zIndex?: number;
}

LiveKitState

interface LiveKitState {
  isConnected: boolean;
  isConnecting: boolean;
  error: string | null;
  participants: number;
  agentConnected: boolean;
}

EmbedButtonVisibilityConfig

interface EmbedButtonVisibilityConfig {
  defaultDelayMs?: number;
  groups?: EmbedButtonGroupConfig[];
}

interface EmbedButtonGroupConfig {
  id: string;
  screens: string[];
  continuity: 'continuous' | 'perScreen';
  delayMs?: number;
  delayPolicy?: 'oncePerGroupEntry' | 'oncePerAppSession' | 'perScreen';
}

Troubleshooting

Widget does not appear

  • Confirm EmbedInitService.initialize() is called in AppComponent.ngOnInit() (not in a lazy-loaded module).
  • Check the browser console for init errors — your API key may be invalid.
  • Verify that currentPath is being updated on navigation and matches one of the values in includeScreens.

Animations are broken or missing

  • Ensure provideAnimations() (standalone) or BrowserAnimationsModule (NgModule) is present in your app config. The SDK components use Angular animations and will silently degrade without this.

Stylesheet is not applied

  • Confirm the widget CSS path is included in your angular.json styles array and that you have rebuilt the application after the change.

Voice call button does nothing

  • livekit-client must be installed as a peer dependency.
  • Check the browser console for LiveKit connection errors. The agent must be provisioned and running in your Revrag dashboard.

Widget appears behind other elements

  • The widget uses a high z-index by default. If your app has elements with a higher stacking context, use the position input to set a custom zIndex:
<revrag-embed-button [position]="{ bottom: '24px', right: '24px', zIndex: 99999 }">
</revrag-embed-button>

Server-side rendering (SSR / Angular Universal)

The SDK uses PlatformService internally to detect the browser platform and skips all DOM and LiveKit operations during SSR. No additional configuration is needed — the widget renders nothing on the server and hydrates cleanly on the client.


License

MIT © Revrag AI