Browse In-App Agent

Angular — Advanced Provider Usage

Advanced <revrag-embed-provider> patterns for @revrag-ai/embed-angular 2.0.0 — route-based visibility, delay policies, group configuration, scoping the agent, positioning, programmatic control and best practices.

<revrag-embed-provider> decides where and when the AI widget shows in your Angular app. You mount it once around your app, and it shows or hides its own floating button as the user moves between routes.

Package@revrag-ai/embed-angular
Version2.0.0
RequiresAngular 16 or newer — standalone components or NgModule
Before you startFinish the Angular integration guide: install (package, animations, stylesheet), initialize with appVersion, and mount the provider once

This page is the pattern catalogue for the provider: where it shows, when, where it sits, and what the agent may see. Installing, initializing, identifying your user, events, the self-check and troubleshooting are in the main Angular guide. Peer dependency versions are on Angular dependencies, and what changed in 2.0.0 is on Angular releases.


Table of Contents

  1. How it works
  2. Level 1 — Show the widget on every page
  3. Level 2 — Show the widget only on specific routes
  4. Level 3 — Delay the widget
  5. Level 4 — Position the button
  6. Level 5 — Group-based visibility
  7. Level 6 — Visibility vs. what the agent can see
  8. Level 7 — Use the current path in your own components
  9. Programmatic control
  10. Complete real-world example
  11. Inputs quick reference
  12. Production best practices
  13. Common mistakes
  14. Which pattern do I need?

How it works

<revrag-embed-provider>
 ├── Starts the agent runtime for this mount
 ├── Finds the current path:
 │     currentPath input  >  Angular Router (NavigationEnd)  >  window.location.pathname
 ├── Hides the button on excludeScreens
 ├── Checks the path against includeScreens + every group's screens (using matchMode)
 ├── Works out the delay (group delayMs > defaultDelayMs > embedButtonDelayMs)
 └── Renders its own floating button when the checks pass
  • If includeScreens is empty and you define no groups, the button shows on every route.
  • If includeScreens or any group lists screens, the button shows only on those routes.
  • The button only appears after EmbedInitService.initialize() has succeeded. While it is loading, or if it fails, nothing is shown.
  • The provider follows the Angular router by itself. It injects Router and checks again on every NavigationEnd, using the URL after redirects without its ?query and #fragment (/help?tab=2#faq is checked as /help). You do not subscribe to the router or pass currentPath.
  • The provider renders its own button. Never add <revrag-embed-button> next to it — that is a second button with its own call.

Hiding the button ends a call. On the web the call lives inside the button, so whenever the button is removed — on a screen outside your list, on an excludeScreens path, or while an entrance delay is running — a call in progress ends. Plan your delays and groups with this in mind (Level 5).


Level 1 — Show the widget on every page

The simplest setup. No route filtering, no delays.

// app.component.ts — initialize once, mount the provider once
import { Component, OnInit } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { EmbedInitService, EmbedProviderComponent } from '@revrag-ai/embed-angular';
import { environment } from '../environments/environment';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, EmbedProviderComponent],
  template: `
    <revrag-embed-provider>
      <router-outlet></router-outlet>
    </revrag-embed-provider>
  `,
})
export class AppComponent implements OnInit {
  constructor(private embedInit: EmbedInitService) {}

  ngOnInit(): void {
    this.embedInit
      .initialize(environment.revragApiKey, { appVersion: environment.appVersion })
      .catch((err: unknown) => console.error('[Revrag] init failed:', err));
  }
}

Required steps:

  1. Call EmbedInitService.initialize(apiKey, { appVersion }) once, in AppComponent.ngOnInit(). Without appVersion, initialization fails and the button never appears.
  2. Wrap your app's content in one <revrag-embed-provider>.
  3. Add provideAnimations() (or BrowserAnimationsModule) and the stylesheet (guide §1).

The button appears on every route once initialization has finished.

NgModule apps: import EmbedModule in your root module and use the same <revrag-embed-provider> template. Every input on this page works the same way, and you do not import the components one by one. The complete NgModule setup is in guide §5B.

All later snippets show only the template or the class fields. initialize() always stays in AppComponent, as above.


Level 2 — Show the widget only on specific routes

Use includeScreens to list the routes that show the button. This is the recommended pattern for router-based apps.

Exact match (default)

/help matches only /help — not /help/faq.

<revrag-embed-provider [includeScreens]="['/offers', '/help', '/support']">
  <router-outlet></router-outlet>
</revrag-embed-provider>

Prefix match — matchMode="startsWith"

/help matches /help, /help/faq, /help/contact, and so on. It does not match /helpdesk — a prefix only counts at a / boundary.

<revrag-embed-provider [includeScreens]="['/help', '/offers']" matchMode="startsWith">
  <router-outlet></router-outlet>
</revrag-embed-provider>
<!-- /help, /help/faq, /offers/123 → all show the button; /helpdesk does not -->

'/' matches only the home page, even with startsWith. To show the button everywhere, leave includeScreens empty.

How to write paths

  • Start with / and leave off any trailing slash: '/help', not 'help' or '/help/'.
  • Write them the way the Angular router reports them — without your <base href>, query string or fragment.
  • Matching is case-sensitive: /Help is not /help.
  • A path your app never reports does not cause an error — the button just never shows there. Log router.url once and write your lists against it.

Keep the lists in environment.ts

Useful when several components share the configuration, or it differs per environment.

// src/environments/environment.ts
export const environment = {
  revragApiKey: 'YOUR_API_KEY',
  appVersion: '1.2.3',
  embed: {
    includeScreens: ['/offers', '/help', '/support'],
    excludeScreens: ['/login', '/otp'],
  },
};
// app.component.ts
import { Component, OnInit } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { EmbedInitService, EmbedProviderComponent } from '@revrag-ai/embed-angular';
import { environment } from '../environments/environment';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, EmbedProviderComponent],
  template: `
    <revrag-embed-provider
      [includeScreens]="embed.includeScreens"
      [excludeScreens]="embed.excludeScreens"
    >
      <router-outlet></router-outlet>
    </revrag-embed-provider>
  `,
})
export class AppComponent implements OnInit {
  embed = environment.embed;

  constructor(private embedInit: EmbedInitService) {}

  ngOnInit(): void {
    this.embedInit
      .initialize(environment.revragApiKey, { appVersion: environment.appVersion })
      .catch((err: unknown) => console.error('[Revrag] init failed:', err));
  }
}

Add the same embed block to every environment file your build swaps in (for example environment.prod.ts). excludeScreens is explained in Level 6.

Screens that are not in the URL — currentPath

Most apps never need this. Use it when the screen is not in the URL — tabs, steps or a dashboard that switches views in state. Map the active view to a synthetic path:

// app.component.ts — tabs switched in state, not by the router
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { EmbedInitService, EmbedProviderComponent } from '@revrag-ai/embed-angular';
import { environment } from '../environments/environment';

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-root',
  standalone: true,
  imports: [CommonModule, EmbedProviderComponent],
  template: `
    <revrag-embed-provider [currentPath]="currentPath" [includeScreens]="['/offers']">
      <div class="tab-bar">
        <button
          *ngFor="let tab of tabs"
          type="button"
          (click)="activeTab = tab.id"
          [class.active]="activeTab === tab.id"
        >
          {{ tab.label }}
        </button>
      </div>
      <ng-container [ngSwitch]="activeTab">
        <div *ngSwitchCase="'dashboard'">Dashboard content</div>
        <div *ngSwitchCase="'offers'">Offers content — the button is visible here</div>
        <div *ngSwitchCase="'settings'">Settings content</div>
      </ng-container>
    </revrag-embed-provider>
  `,
})
export class AppComponent implements OnInit {
  tabs = TABS;
  activeTab = 'dashboard';

  constructor(private embedInit: EmbedInitService) {}

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

  ngOnInit(): void {
    this.embedInit
      .initialize(environment.revragApiKey, { appVersion: environment.appVersion })
      .catch((err: unknown) => console.error('[Revrag] init failed:', err));
  }
}

Things to know about currentPath:

  • While it has a value, the provider ignores router navigation.
  • It is used exactly as given — a query string or fragment is not removed, so pass a clean path.
  • The agent receives it as the current screen. Keep the values stable and free of personal data (never a user id or account number).
  • Without a router and without currentPath, the provider reads window.location.pathname only once, when it starts.

To name screens for the agent without changing where the button shows (for example wizard steps on one URL), use getScreenName instead — see guide §8.


Level 3 — Delay the widget

Show the button after the user has been on a screen for a few seconds, so it does not feel pushy.

<revrag-embed-provider [includeScreens]="['/pricing', '/checkout']" [embedButtonDelayMs]="4000">
  <router-outlet></router-outlet>
</revrag-embed-provider>

What happens:

  1. User navigates to /pricing → the button is hidden and a 4-second timer starts.
  2. The timer fires → the button appears.
  3. User navigates to /checkout → the button hides again and a new 4-second timer starts. A plain delay restarts on every navigation.
  4. User navigates to /home (not included) → the button hides and the timer is cancelled.
  5. User comes back to /pricing → the 4-second timer starts again.

Because step 3 hides the button, a call running on /pricing ends when the user moves to /checkout. A navigation that only changes the query string (/pricing?plan=pro) restarts the delay too. If users move between these screens during a call, use a group with continuity: 'continuous' instead (Level 5).


Level 4 — Position the button

Fixed position (default)

By default the button floats 24px from the bottom and 24px from the right of the window. Change that with embedButtonPosition:

<revrag-embed-provider [embedButtonPosition]="{ bottom: 40, right: 24 }">
  <router-outlet></router-outlet>
</revrag-embed-provider>
  • Numbers are pixels. CSS strings also work: { bottom: '5rem', right: '1.5rem' }.
  • A side you leave out falls back to 24px.
  • embedButtonPosition only has bottom and right. A fixed button on the left comes from your dashboard configuration (below).

To lift the button above a bottom tab bar, use widgetBottomOffset (pixels). It is added to the bottom value:

<!-- Button sits 24px + 80px = 104px from the bottom -->
<revrag-embed-provider [widgetBottomOffset]="80">
  <router-outlet></router-outlet>
</revrag-embed-provider>

widgetBottomOffset adds pixels to the number at the start of the bottom value. Use it with pixel values only — combined with bottom: '5rem' it produces 69px for an offset of 64, not 5rem + 64px.

In fixed mode the SDK moves the widget's element to the end of <body>, so a parent with transform, filter or overflow: hidden cannot trap it.

Embedded position

widgetPositioning="embedded" puts the widget into your page layout instead of floating it over the page. It renders inside <revrag-embed-provider>, after your content, at full width. Use widgetSide to align it left or right (it is right-aligned without it):

<section class="help-panel">
  <revrag-embed-provider widgetPositioning="embedded" widgetSide="left">
    <app-help-panel></app-help-panel>
  </revrag-embed-provider>
</section>

embedButtonPosition and widgetBottomOffset have no effect in embedded mode, and an embedded widget cannot be dragged.

Which position wins

  1. A position set in your Revrag dashboard's widget configuration wins over every input. It picks the corner (top or bottom; left, right or centre) and the gap from each edge. It replaces embedButtonPosition and widgetBottomOffset completely, and in embedded mode it also decides the alignment instead of widgetSide. If the dashboard sets no position, the inputs below apply.
  2. Fixed mode: embedButtonPosition, plus widgetBottomOffset. widgetSide does not move a fixed button.
  3. Embedded mode: widgetSide ('left' or 'right').

Users can drag the fixed button; it snaps to the left or right edge. The provider has no input to turn this off — your position is where the button starts.

widgetClassName is accepted but is not applied to any element in 2.0.0, so it cannot be used to style the widget.


Level 5 — Group-based visibility

This is the advanced visibility engine. Use it when different parts of your app need different delay or continuity behaviour.

The problem it solves

With a plain delay, every navigation restarts the timer. If a user moves from /checkout to /payment (both part of checkout), the button keeps hiding and coming back — and any call in progress ends. Groups prevent that.

Core concepts

continuity — what happens when the user moves inside a group (required):

ValueBehaviour
'continuous'Coming from another screen in the same group, the button stays visible — no delay, no hiding, and a live call keeps running.
'perScreen'Every screen is handled on its own, using delayPolicy.

delayPolicy — when the delay runs (default 'perScreen'):

ValueBehaviour
'perScreen'The delay runs on every screen in the group.
'oncePerGroupEntry'The delay runs when the user enters the group from a screen outside it. Moving between screens inside the group shows the button at once. Leaving the group and coming back counts as a new entry.
'oncePerAppSession'The delay runs until the button has appeared once for this group. After that, the group shows the button with no delay.

How long "once" lasts for 'oncePerAppSession': the provider keeps this memory in the browser tab, per provider. A full page reload, or destroying and re-creating the provider, starts over. If the user leaves before the delay finishes, the button never appeared, so the next visit delays again.

For a continuous group, 'perScreen' and 'oncePerGroupEntry' behave the same: the delay runs when the user enters the group, and never while they move inside it.

Group rules

  • Screens in groups[].screens are added to includeScreens for you. You do not repeat them — but this also means that once you define any group, the button shows only on group screens and includeScreens. Add any other paths you want to includeScreens.
  • Group membership is an exact path match, even with matchMode="startsWith". A group listing '/checkout' does not cover /checkout/address. With startsWith the button still shows on /checkout/address, but as a screen outside the group — it uses defaultDelayMs / embedButtonDelayMs, and moving between it and /checkout breaks the group's continuity. List every screen of a journey.
  • If a path is in two groups, the first group in the array wins.
  • Delay order: the group's delayMs, then defaultDelayMs, then embedButtonDelayMs. Set a group's delayMs: 0 to turn the delay off for that group.
  • Screens outside every group always use the 'perScreen' policy: their delay (if any) runs on every navigation.

Basic groups example

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { EmbedProviderComponent } from '@revrag-ai/embed-angular';
import type { EmbedButtonVisibilityConfig } from '@revrag-ai/embed-angular';

@Component({
  selector: 'app-shell',
  standalone: true,
  imports: [RouterOutlet, EmbedProviderComponent],
  template: `
    <revrag-embed-provider [embedButtonVisibilityConfig]="visibilityConfig">
      <router-outlet></router-outlet>
    </revrag-embed-provider>
  `,
})
export class AppShellComponent {
  visibilityConfig: EmbedButtonVisibilityConfig = {
    groups: [
      {
        id: 'checkout-flow',
        screens: ['/cart', '/checkout', '/payment', '/confirmation'],
        continuity: 'continuous', // stays visible between checkout steps
        delayMs: 2000,
        delayPolicy: 'oncePerGroupEntry', // delay each time the user enters checkout
      },
    ],
  };
}

What happens:

  1. User is on /home → button hidden (not in any group, and no includeScreens).
  2. User goes to /cart → 2-second delay, then the button appears.
  3. User goes to /checkout → the button stays visible (same group, continuous).
  4. User goes to /payment → the button stays visible.
  5. User leaves to /home → button hidden.
  6. User comes back to /cart → this is a new entry, so the 2-second delay runs again.

Want the delay only the first time? Use delayPolicy: 'oncePerAppSession'. Then step 6 shows the button straight away.

Multiple groups

visibilityConfig: EmbedButtonVisibilityConfig = {
  groups: [
    {
      id: 'onboarding',
      screens: ['/welcome', '/setup', '/profile-setup'],
      continuity: 'continuous',
      delayMs: 5000,
      delayPolicy: 'oncePerAppSession', // delays only until it has appeared once
    },
    {
      id: 'checkout',
      screens: ['/cart', '/checkout', '/payment'],
      continuity: 'continuous',
      delayMs: 2000,
      delayPolicy: 'oncePerGroupEntry',
    },
    {
      id: 'support',
      screens: ['/help', '/faq', '/contact'],
      continuity: 'perScreen', // each support page on its own
      delayMs: 3000,
      delayPolicy: 'perScreen', // 3 s delay on every support page
    },
  ],
};

With only groups (no includeScreens), the button shows on these nine screens and nowhere else.

defaultDelayMs

defaultDelayMs is used for any included screen that has no group, and for any group that leaves out delayMs. It wins over embedButtonDelayMs.

includeScreens = ['/dashboard', '/settings', '/help'];

visibilityConfig: EmbedButtonVisibilityConfig = {
  defaultDelayMs: 2000, // used by /dashboard and /settings
  groups: [
    {
      id: 'support',
      screens: ['/help'], // /help uses its own group delay
      continuity: 'perScreen',
      delayMs: 5000,
      delayPolicy: 'perScreen',
    },
  ],
};
<revrag-embed-provider [includeScreens]="includeScreens" [embedButtonVisibilityConfig]="visibilityConfig">
  <router-outlet></router-outlet>
</revrag-embed-provider>

Here /dashboard and /settings wait 2 seconds on every visit, and /help waits 5 seconds.

Groups plus other screens

Onboarding flows, checkout funnels or support pages often need a delay, while the rest of the app shows the button straight away:

// Screens outside every group that should still show the button, with no delay.
otherScreens = ['/', '/accounts'];

visibility: EmbedButtonVisibilityConfig = {
  groups: [
    // The checkout journey: 2 s delay on entry, none between steps, and a live call keeps running.
    {
      id: 'checkout',
      screens: ['/checkout', '/payment', '/confirmation'],
      continuity: 'continuous',
      delayMs: 2000,
      delayPolicy: 'oncePerGroupEntry',
    },

    // Support pages: a 2 s delay until the button has appeared once (until the page reloads).
    {
      id: 'support',
      screens: ['/support', '/help'],
      continuity: 'perScreen',
      delayMs: 2000,
      delayPolicy: 'oncePerAppSession',
    },
  ],
};
<revrag-embed-provider [includeScreens]="otherScreens" [embedButtonVisibilityConfig]="visibility">
  <router-outlet></router-outlet>
</revrag-embed-provider>

Entering a delayed group from a visible screen ends a call. A user on /accounts (button visible) who opens /checkout gets the 2-second entry delay — the button hides, so a call in progress ends. If users enter a flow mid-call, give that group delayMs: 0.


Level 6 — Visibility vs. what the agent can see

includeScreens controls where the button shows. It does not, on its own, limit what the agent can see and act on. Two more inputs control that:

InputDefaultEffect
excludeScreens—On these screens the button is hidden, the agent stops seeing the page, nothing is sent, and a live call ends. Always a prefix match ('/settings' also covers /settings/security), whatever matchMode says.
showOnAllScreenstruetrue: the agent works everywhere except excludeScreens. false: the agent works only on includeScreens and group screens, and excludeScreens is ignored.

Deny-list (default) — everywhere except a few screens. Best for login, OTP, PIN, payment and security screens:

<revrag-embed-provider [excludeScreens]="['/login', '/otp', '/settings/security']">
  <router-outlet></router-outlet>
</revrag-embed-provider>

Allow-list — only a few screens:

<revrag-embed-provider
  [showOnAllScreens]="false"
  [includeScreens]="['/', '/accounts', '/loans']"
  matchMode="startsWith"
>
  <router-outlet></router-outlet>
</revrag-embed-provider>

Pick one mode. With [showOnAllScreens]="false", excludeScreens is not read at all — a screen listed in both stays in scope.

  • In allow-list mode the agent's list is always a prefix match, whatever matchMode says. With the default 'exact', '/accounts' shows the button only on /accounts, but the agent may also see /accounts/123.
  • Inside a screen, mark fields the agent must never read with data-revrag-mask, and narrow the agent to one region of the page with actionRoot — see guide §4.

Level 7 — Use the current path in your own components

The provider does not expose its path, and Angular does not need a hook for this: read it from the router the same way the provider does.

import { Component } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-breadcrumb',
  standalone: true,
  template: `<nav>You are at: {{ path }}</nav>`,
})
export class BreadcrumbComponent {
  constructor(private router: Router) {}

  get path(): string {
    // Same rule as the provider: drop the query string and fragment
    return this.router.url.split(/[?#]/)[0] || '/';
  }
}

If you pass currentPath to the provider, use that same value in your components.


Programmatic control

Start and end calls, and open or close the widget, from your own buttons. The full method list is in guide §6.

import { Component, NgZone, OnDestroy, OnInit } from '@angular/core';
import {
  EmbedCallService,
  collapseWidget,
  expandWidget,
  isWidgetExpanded,
  subscribeWidgetExpanded,
} from '@revrag-ai/embed-angular';

@Component({
  selector: 'app-assistant-controls',
  standalone: true,
  template: `
    <button type="button" (click)="talk()">Talk to an assistant</button>
    <button type="button" (click)="hangUp()">End call</button>
    <button type="button" (click)="toggle()">{{ expanded ? 'Close' : 'Open' }} assistant</button>
  `,
})
export class AssistantControlsComponent implements OnInit, OnDestroy {
  expanded = isWidgetExpanded();
  private unsubscribe?: () => void;

  constructor(private calls: EmbedCallService, private zone: NgZone) {}

  ngOnInit(): void {
    // The listener can run outside Angular's zone, so re-enter it before updating the view
    this.unsubscribe = subscribeWidgetExpanded((open) =>
      this.zone.run(() => (this.expanded = open)),
    );
  }

  ngOnDestroy(): void {
    this.unsubscribe?.();
  }

  async talk(): Promise<void> {
    if (!this.calls.isCallActive()) await this.calls.startCall();
  }

  hangUp(): void {
    this.calls.endCall();
  }

  toggle(): void {
    if (isWidgetExpanded()) collapseWidget();
    else expandWidget();
  }
}

Things to know:

  • startCall() only works while the button is on the page. On a hidden screen, or while a delay timer runs, it logs [RevRag] Cannot startCall: SDK not initialized (no EmbedProvider mounted) and does nothing — even though the provider is mounted. Show your own call button only on screens where the widget is visible.
  • Call startCall() from a click, so the browser can show the microphone prompt.
  • subscribeWidgetExpanded does not call your listener straight away. Read the starting value with isWidgetExpanded().
  • These controls work with the provider's own button. You never need <revrag-embed-button> for them.

Complete real-world example

An app with onboarding and checkout flows, each with its own widget behaviour, and one screen the agent must never see.

// app.component.ts
import { Component, OnInit } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { EmbedInitService, EmbedProviderComponent } from '@revrag-ai/embed-angular';
import type { EmbedButtonVisibilityConfig } from '@revrag-ai/embed-angular';
import { environment } from '../environments/environment';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, EmbedProviderComponent],
  template: `
    <revrag-embed-provider
      [includeScreens]="includeScreens"
      matchMode="startsWith"
      [embedButtonVisibilityConfig]="visibilityConfig"
      [excludeScreens]="excludeScreens"
      [embedButtonPosition]="{ bottom: 24, right: 24 }"
      [widgetBottomOffset]="64"
    >
      <router-outlet></router-outlet>
    </revrag-embed-provider>
  `,
})
export class AppComponent implements OnInit {
  // Prefix match: /dashboard/** and /help/** show the button straight away
  includeScreens = ['/dashboard', '/help'];

  // The agent never sees these screens; a call ends if the user opens one
  excludeScreens = ['/settings/security'];

  visibilityConfig: EmbedButtonVisibilityConfig = {
    groups: [
      {
        // Onboarding: delay until it has appeared once, stay visible through all steps
        id: 'onboarding',
        screens: ['/welcome', '/setup', '/verify'],
        continuity: 'continuous',
        delayMs: 6000,
        delayPolicy: 'oncePerAppSession',
      },
      {
        // Checkout: delay on each entry, no hiding between steps.
        // Group match is exact — list every step, not just a prefix.
        id: 'checkout',
        screens: ['/cart', '/checkout', '/payment', '/order-confirmed'],
        continuity: 'continuous',
        delayMs: 3000,
        delayPolicy: 'oncePerGroupEntry',
      },
    ],
  };

  constructor(private embedInit: EmbedInitService) {}

  ngOnInit(): void {
    this.embedInit
      .initialize(environment.revragApiKey, { appVersion: environment.appVersion })
      .catch((err: unknown) => console.error('[Revrag] init failed:', err));
  }
}

Why it is set up this way:

  • The provider sits in the root component, so it is created once and never destroyed by navigation.
  • Screens outside any group have no delay, so moving between dashboard pages never hides the button mid-call.
  • Delays are only used on continuous groups, so they run on entry and never while the user moves inside the flow. Entering a flow from a dashboard page still hides the button for the delay — set that group's delayMs: 0 if users start checkout mid-call.
  • widgetBottomOffset lifts the button above a 64px bottom bar (24 + 64 = 88px from the bottom), unless your dashboard sets a position.

Inputs quick reference

Selector: revrag-embed-provider. Every input is optional.

InputTypeDefaultPurpose
includeScreensstring[][] (all routes)Routes where the button appears. Group screens are added to it.
matchMode'exact' | 'startsWith''exact'How includeScreens (and group screens) match for showing the button. Group membership is always exact.
embedButtonDelayMsnumber0Delay (ms) before the button appears, used when no group delayMs or defaultDelayMs applies.
embedButtonVisibilityConfigEmbedButtonVisibilityConfig—Groups, continuity and delay policies (Level 5).
currentPathstring—Path override. Wins over the router. Used exactly as given.
getScreenName() => string | null | undefined—Gives the agent a screen name that is not in the URL. Return null to use the URL. Does not affect where the button shows (guide §8).
excludeScreensstring[]—Screens where the button hides, the agent stops seeing the page and a call ends. Prefix match. Ignored when showOnAllScreens is false.
showOnAllScreensbooleantruefalse turns includeScreens (plus group screens) into the agent's allow-list.
actionRootElementRef | HTMLElementdocument.bodyThe part of the page the agent can see (guide §4).
widgetPositioning'fixed' | 'embedded''fixed'Float over the page, or sit in your layout after the provider's content.
embedButtonPositionEmbedButtonPosition— (24px from the bottom and right){ bottom?, right? }: position of the fixed button. Numbers are px, strings are used as-is; a side you leave out is 24px.
widgetBottomOffsetnumber0Extra px added to the bottom position in fixed mode.
widgetSide'left' | 'right'—Alignment in embedded mode only.
widgetClassNamestring''Accepted, but not applied to any element in 2.0.0.

A position set in your dashboard's widget configuration replaces embedButtonPosition, widgetBottomOffset and (in embedded mode) widgetSide — see Which position wins.

EmbedButtonVisibilityConfig

FieldTypeDescription
defaultDelayMsnumberDelay for included screens with no group, and for groups without delayMs. Wins over embedButtonDelayMs.
groupsEmbedButtonGroupConfig[]The groups. The first matching group wins.

EmbedButtonGroupConfig

FieldTypeRequiredDescription
idstringYesUnique, stable name for the group.
screensstring[]YesExact paths in the group. Added to includeScreens for you.
continuityEmbedButtonContinuity ('continuous' | 'perScreen')YesSee Core concepts.
delayMsnumberNoDelay for this group. 0 means no delay. Wins over defaultDelayMs and embedButtonDelayMs.
delayPolicyEmbedButtonDelayPolicy ('perScreen' | 'oncePerGroupEntry' | 'oncePerAppSession')No ('perScreen')When the delay runs.

All five types (EmbedButtonVisibilityConfig, EmbedButtonGroupConfig, EmbedButtonPosition, EmbedButtonDelayPolicy, EmbedButtonContinuity) are exported from @revrag-ai/embed-angular. Import them with import type.


Production best practices

  1. One provider, in the root component. Not inside a routed or lazy-loaded page — navigation would destroy it, removing the button, ending calls and resetting oncePerAppSession memory. Two providers mean two buttons.
  2. Let the provider follow the router. Pass currentPath only for tab- or state-driven screens.
  3. Initialize once, with appVersion from your build, in AppComponent.ngOnInit(), and catch the promise. You do not need to wait for isInitialized$ before rendering — the provider shows the button by itself once initialization succeeds. Log error$ from EmbedInitService: it is where initialization failures surface.
  4. Keep delays off screens users visit during a call. Every time the button hides — including during a delay, and on a navigation that only changes the query string — a running call ends. Put delayed flows in a continuous group.
  5. Use continuous groups for multi-step flows — no blink, no re-delay, and a live call keeps running through /checkout → /payment → /confirmation. List every path of the flow, because group membership is exact.
  6. oncePerGroupEntry for funnels, oncePerAppSession for support screens, so the delay does not repeat on every visit.
  7. Use excludeScreens for sensitive pages (login, OTP, security settings, admin) rather than just leaving them out of includeScreens — leaving a page out hides the button but does not stop the agent from seeing it. Mask sensitive fields with data-revrag-mask.
  8. Set visibility inputs once. If you change includeScreens, excludeScreens, matchMode, the delays or embedButtonVisibilityConfig at runtime, the button picks up the change on the user's next navigation (a new excludeScreens entry can hide the button's content at once, but a running call only ends on the next navigation). What the agent may see is updated at once, but only when includeScreens, excludeScreens or showOnAllScreens change. To clear excludeScreens, pass [] — setting it back to undefined keeps the old list.
  9. Label icon-only buttons with aria-label — the agent finds controls by their accessible name.
  10. Write your lists against the paths your app actually reports. A path that never occurs silently hides the button; log router.url once to check.
  11. Call EmbedLogout() when the user signs out — see On logout.
  12. Check your setup before release with verifyIntegration() from @revrag-ai/embed-angular/diagnostics (guide §9).

Common mistakes

Adding <revrag-embed-button> next to the provider:

<!-- Wrong: two buttons on screen, each with its own call -->
<revrag-embed-provider>
  <router-outlet></router-outlet>
  <revrag-embed-button></revrag-embed-button>
</revrag-embed-provider>

<!-- Right: the provider renders its own button -->
<revrag-embed-provider>
  <router-outlet></router-outlet>
</revrag-embed-provider>

With two buttons, EmbedCallService controls only one of them. The same applies to the inner components (EmbedWidgetComponent, <revrag-embed-widget> and the others): they are deprecated in 2.0.0, still exported for apps upgrading from 1.0.7, and the provider already renders them.

Feeding currentPath from the router yourself:

The provider already listens to the router. Copying router.url into currentPath is not needed, and it keeps the query string — so /help?ref=email no longer matches '/help'.

<!-- Wrong: not needed, and breaks matching on URLs with a query string -->
<revrag-embed-provider [currentPath]="router.url" [includeScreens]="['/help']">

<!-- Right: let the provider read the router -->
<revrag-embed-provider [includeScreens]="['/help']">

Expecting a group to cover sub-routes:

// Wrong: /checkout/address is NOT in this group, even with matchMode="startsWith"
{ id: 'checkout', screens: ['/checkout'], continuity: 'continuous', delayMs: 2000 }

// Right: list every step
{ id: 'checkout', screens: ['/checkout', '/checkout/address', '/checkout/pay'], continuity: 'continuous', delayMs: 2000 }

Using '/' with startsWith to mean "everywhere": it matches only the home page. Leave includeScreens empty instead.

Expecting includeScreens to hide pages from the agent: it only controls the button. Use excludeScreens, or [showOnAllScreens]="false" for an allow-list (Level 6).

Using excludeScreens together with [showOnAllScreens]="false": in allow-list mode excludeScreens is ignored. Remove the page from includeScreens instead.

Expecting widgetSide="left" to move the fixed button: widgetSide only aligns the widget in embedded mode. A left-side fixed button comes from your dashboard's widget configuration.

Styling the widget with widgetClassName: it has no effect in 2.0.0.

Repeating group screens in includeScreens: not wrong, just not needed — group screens are added for you.

// No includeScreens needed: /help is already in the group
visibilityConfig: EmbedButtonVisibilityConfig = {
  groups: [{ id: 'support', screens: ['/help'], continuity: 'perScreen' }],
};

Which pattern do I need?

You wantUseSection
The button on every pageNo inputsLevel 1
The button on a known set of routes (recommended)includeScreensLevel 2
A whole route area (/help/**)matchMode="startsWith"Level 2
Shared, environment-driven configLists in environment.tsLevel 2
Tabs or steps without the routercurrentPathLevel 2
One uniform delayembedButtonDelayMsLevel 3
A different corner, or room for a tab barembedButtonPosition, widgetBottomOffsetLevel 4
The widget inside your layoutwidgetPositioning="embedded"Level 4
Delays and journeys that keep a call aliveembedButtonVisibilityConfig groupsLevel 5
Keep the agent off login, OTP or payment screensexcludeScreens, or the allow-listLevel 6
Your own call or open/close buttonsEmbedCallService, expandWidget()Programmatic control