Skip to main content

Angular — Advanced Provider Usage

Advanced EmbedProviderComponent patterns — route-based visibility, delay policies, group configuration, programmatic control, and best practices.

Table of Contents

  1. Quick Start
  2. EmbedProviderComponent — Props Reference
  3. embedButtonVisibilityConfig
  4. EmbedButtonComponent Props
  5. EmbedInitService
  6. Usage Patterns
  7. Best Practices

Quick Start

// 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');
  }
}
// app-shell.component.ts
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', '/support']"
      matchMode="exact"
    >
      <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));
  }
}
Required steps:
  1. Call EmbedInitService.initialize(apiKey) in AppComponent.ngOnInit().
  2. Wrap your layout with <revrag-embed-provider>.
  3. Keep currentPath in sync with the Angular router.
  4. Add the CSS to angular.json styles array.

EmbedProviderComponent — Props Reference

Selector: revrag-embed-provider
InputTypeDefaultDescription
currentPathstring | undefinedundefinedHighest-priority path override. Keep in sync with the Angular router (or active tab). When omitted the widget is always hidden.
includeScreensstring[][]Paths where the embed button is visible. Empty = never shown via provider (use EmbedButtonComponent directly).
matchMode'exact' | 'startsWith''exact'exact — path must match exactly. startsWith — path and all sub-routes (e.g. /help matches /help/faq).
embedButtonDelayMsnumber0Global delay (ms) before showing the button. Applies when no group config matches.
embedButtonVisibilityConfigEmbedButtonVisibilityConfig | undefinedundefinedPer-group config for delays and continuity. See below.
embedButtonPositionEmbedButtonPosition | undefinedundefinedOverride the button’s pixel position (bottom, right).
widgetPositioning'fixed' | 'embedded''fixed'fixed — viewport-fixed. embedded — in normal document flow.
widgetSide'left' | 'right' | undefinedundefinedHorizontal alignment of the widget.
widgetBottomOffsetnumber0Extra pixels to raise the widget from the bottom.
widgetClassNamestring''Extra CSS class applied to the widget container.

Path detection

Unlike React’s EmbedProvider (which auto-detects window.location.pathname), Angular’s EmbedProviderComponent requires you to supply currentPath explicitly because Angular’s router is service-based. Always keep it in sync with NavigationEnd events:
this.router.events
  .pipe(filter((e): e is NavigationEnd => e instanceof NavigationEnd))
  .subscribe((e) => {
    this.currentPath = e.urlAfterRedirects;
  });

embedButtonVisibilityConfig

Use this for per-route or per-group behavior — different delays, policies, and continuity settings.
import { EmbedButtonVisibilityConfig } from '@revrag-ai/embed-angular';

visibilityConfig: EmbedButtonVisibilityConfig = {
  defaultDelayMs: 1500,
  groups: [
    {
      id: 'perScreen',
      screens: ['/offers'],
      continuity: 'perScreen',
      delayMs: 2000,
      delayPolicy: 'perScreen',
    },
    {
      id: 'oncePerGroup',
      screens: ['/checkout', '/payment'],
      continuity: 'perScreen',
      delayMs: 2000,
      delayPolicy: 'oncePerGroupEntry',
    },
    {
      id: 'oncePerSession',
      screens: ['/support'],
      continuity: 'perScreen',
      delayMs: 2000,
      delayPolicy: 'oncePerAppSession',
    },
  ],
};
<revrag-embed-provider
  [currentPath]="currentPath"
  [includeScreens]="allScreens"
  [embedButtonVisibilityConfig]="visibilityConfig"
>
  <router-outlet></router-outlet>
</revrag-embed-provider>

EmbedButtonVisibilityConfig

FieldTypeDescription
defaultDelayMsnumberFallback delay when a group matches but has no delayMs.
groupsEmbedButtonGroupConfig[]Per-group configuration array.

EmbedButtonGroupConfig

FieldTypeDescription
idstringUnique ID for the group (used for session/continuity logic).
screensstring[]Paths belonging to this group.
continuity'perScreen' | 'continuous'See EmbedButtonContinuity below.
delayMsnumberDelay (ms) before showing the button when this group matches.
delayPolicyEmbedButtonDelayPolicyWhen to apply the delay. See below.

EmbedButtonContinuity

ValueBehavior
'perScreen'Treat each screen in the group as a separate visit. Delays apply per screen according to delayPolicy.
'continuous'Once you enter the group, the button stays visible while navigating within the group. No delay on subsequent screens in the same group.

EmbedButtonDelayPolicy

ValueWhen delay applies
'perScreen'Every time you land on any screen in the group.
'oncePerGroupEntry'Only when first entering the group from outside. No delay when moving between screens within the group.
'oncePerAppSession'Only the first time you visit any screen in this group during the session. Page refresh resets.

EmbedButtonComponent Props

Props for the standalone <revrag-embed-button> component (also forwarded internally by EmbedProviderComponent).
InputTypeDefaultDescription
positioning'fixed' | 'embedded''fixed'fixed — viewport-fixed. embedded — in-flow.
side'left' | 'right' | 'center' | undefinedundefinedAuto-position in bottom-left, bottom-right, or bottom-center.
bottomOffsetnumber0Extra offset from bottom (e.g. for nav bars).
positionPositionConfig | undefinedundefinedFine-grained CSS position override.
classNamestring''Additional CSS class for the button container.

PositionConfig

interface PositionConfig {
  bottom?: string;    // e.g. '24px'
  right?: string;     // e.g. '24px'
  left?: string;
  top?: string;
  transform?: string;
  zIndex?: number;
}

Example

<revrag-embed-provider
  [currentPath]="currentPath"
  [includeScreens]="['/offers']"
  widgetPositioning="fixed"
  widgetSide="right"
  [widgetBottomOffset]="80"
>
  <router-outlet></router-outlet>
</revrag-embed-provider>

EmbedInitService

Initializes the SDK. Call once in AppComponent.ngOnInit() before any widget renders.
import { EmbedInitService } from '@revrag-ai/embed-angular';

constructor(private embedInit: EmbedInitService) {}

// Initialize
await this.embedInit.initialize('your_api_key', {
  baseUrl: 'https://custom.api.example.com', // optional
  enableTracker: true,                        // optional
  trackerCallback: (event) => { ... },        // optional
});

SDKConfig options

FieldTypeDescription
baseUrlstringOverride API base URL.
enableTrackerbooleanEnable analytics/tracking.
trackerCallback(event: EventPayload) => voidCustom callback for tracking events.

Return / Observables

Observable / GetterTypeDescription
isInitialized$Observable<boolean>Emits true after successful init.
isLoading$Observable<boolean>Emits true while init is in progress.
error$Observable<string | null>Emits error message if init fails, null otherwise.
sessionData$Observable<SessionStorageData>Stored session/config for debugging.
isInitializedboolean (getter)Synchronous check.

Usage Patterns

Pattern 1 — Manual conditional rendering

Best for: apps where you want explicit, imperative control over when the widget appears. Use *ngIf to conditionally render EmbedButtonComponent 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 /offers -->
    <revrag-embed-button
      *ngIf="showEmbed"
      positioning="fixed"
      side="right"
    ></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 === '/offers';
      });
  }
}
Pros: Simple, explicit, no extra context. Cons: Pathnames are hardcoded next to *ngIf; adding/removing screens means editing the same conditional.

Pattern 2 — EmbedProvider with includeScreens

Best for: router-based apps where the widget should appear on a known set of routes. The provider handles route matching, delay logic, and button lifecycle automatically.
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));
  }
}
To show the widget on /help and all sub-routes like /help/faq, use matchMode="startsWith":
<revrag-embed-provider
  [currentPath]="currentPath"
  [includeScreens]="['/offers', '/help']"
  matchMode="startsWith"
>

Pattern 3 — Object-based config

Best for: teams that prefer keeping widget configuration in a separate object (easier to share across components, load from a config service, or drive from environment variables).
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"
    >
      <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));
  }
}

Config from a service or environment

// embed.config.ts
export const EMBED_CONFIG = {
  includeScreens: (import.meta.env['VITE_EMBED_SCREENS'] ?? '/offers,/help').split(','),
  matchMode: 'exact' as const,
};
For Angular CLI apps with environment.ts:
// environments/environment.ts
export const environment = {
  embedApiKey: 'your_api_key',
  embedScreens: ['/offers', '/help', '/support'],
};
// app.component.ts
import { environment } from '../environments/environment';

ngOnInit(): void {
  this.embedInit.initialize(environment.embedApiKey);
}
<revrag-embed-provider
  [currentPath]="currentPath"
  [includeScreens]="embedScreens"
>
embedScreens = environment.embedScreens;

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 as currentPath. 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;
  }
}

Pattern 5 — With delay and embedButtonVisibilityConfig

Best for: onboarding flows, checkout funnels, or support pages where you want the widget to appear after a delay — but only once per session or per group entry.
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs';
import {
  EmbedProviderComponent,
  type EmbedButtonVisibilityConfig,
} from '@revrag-ai/embed-angular';

@Component({
  selector: 'app-shell',
  standalone: true,
  imports: [CommonModule, RouterModule, EmbedProviderComponent],
  template: `
    <revrag-embed-provider
      [currentPath]="currentPath"
      [includeScreens]="allScreens"
      [embedButtonVisibilityConfig]="visibilityConfig"
      widgetPositioning="fixed"
    >
      <router-outlet></router-outlet>
    </revrag-embed-provider>
  `,
})
export class AppShellComponent {
  currentPath = '/';

  allScreens = ['/offers', '/checkout', '/payment', '/support', '/help'];

  visibilityConfig: EmbedButtonVisibilityConfig = {
    defaultDelayMs: 1500,   // fallback if a screen isn't in any group
    groups: [
      {
        // Shows with a 2s delay, every time the user lands here
        id: 'perScreen',
        screens: ['/offers'],
        continuity: 'perScreen',
        delayMs: 2000,
        delayPolicy: 'perScreen',
      },
      {
        // Shows with a 2s delay only when entering the checkout flow;
        // no delay when moving between /checkout and /payment
        id: 'checkout-flow',
        screens: ['/checkout', '/payment'],
        continuity: 'continuous',
        delayMs: 2000,
        delayPolicy: 'oncePerGroupEntry',
      },
      {
        // Shows with a 2s delay only once per browser session
        id: 'support',
        screens: ['/support', '/help'],
        continuity: 'perScreen',
        delayMs: 2000,
        delayPolicy: 'oncePerAppSession',
      },
    ],
  };

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

Pattern 6 — startsWith for nested routes

Best for: feature areas with nested routes where the widget should appear on any sub-page.
@Component({
  template: `
    <revrag-embed-provider
      [currentPath]="currentPath"
      [includeScreens]="['/help', '/offers']"
      matchMode="startsWith"
      widgetPositioning="fixed"
    >
      <router-outlet></router-outlet>
    </revrag-embed-provider>
  `,
})
export class AppShellComponent {
  // /help, /help/faq, /help/contact → all show the widget
  // /offers, /offers/123, /offers/details → all show the widget
}

Pattern 7 — NgModule-based Apps

For applications that have not migrated to standalone components, import EmbedModule in your 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';
import { AppShellComponent } from './app-shell.component';

@NgModule({
  declarations: [AppComponent, AppShellComponent],
  imports: [
    BrowserModule,
    BrowserAnimationsModule, // required
    EmbedModule,             // registers all embed selectors
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}
All component selectors (revrag-embed-button, revrag-embed-provider) and inputs remain the same. You do not need to import individual components when EmbedModule is imported.

Pattern 8 — With global delay (embedButtonDelayMs)

Use the simple embedButtonDelayMs input when you want a uniform delay across all included screens without per-group config:
<revrag-embed-provider
  [currentPath]="currentPath"
  [includeScreens]="['/offers', '/help']"
  [embedButtonDelayMs]="3000"
  widgetPositioning="fixed"
>
  <router-outlet></router-outlet>
</revrag-embed-provider>
The widget will wait 3 seconds after any matching route becomes active before appearing.

Best Practices

  1. Keep currentPath in sync — Always update it from NavigationEnd.urlAfterRedirects, not NavigationStart, to ensure the URL reflects the fully resolved route.
  2. Use matchMode: 'startsWith' for nested routes — Matches /help plus any sub-path like /help/faq. Use 'exact' when you need precise control.
  3. Initialize first — Always call EmbedInitService.initialize() in AppComponent.ngOnInit() and gate widget rendering on isInitialized$.
  4. Handle errors — Subscribe to error$ from EmbedInitService and surface it to the user or log it.
  5. Add CSS to angular.json — Import node_modules/@revrag-ai/embed-angular/styles/widget.css in the styles array; rebuild after adding it.
  6. Add provideAnimations() — Place it in app.config.ts (standalone) or import BrowserAnimationsModule (NgModule). Missing it breaks widget transitions silently.
  7. Use embedButtonVisibilityConfig for per-route delay policies — Prefer per-group delays over a single global embedButtonDelayMs in multi-route apps.
  8. oncePerAppSession for support screens — Avoids re-showing the delay on every visit during a session.
  9. oncePerGroupEntry for funnels — Delay only when a user first enters a checkout or onboarding flow; no delay when they move between steps.
  10. continuous for multi-step flows — Prevents the widget from disappearing and reappearing as the user navigates through /checkout/payment/confirmation.
  11. Clean up subscriptions — Unsubscribe from router event subscriptions (or use takeUntilDestroyed()) in ngOnDestroy() to prevent memory leaks.
  12. Use embedButtonPosition for fine-grained placement — When the default widgetSide + widgetBottomOffset isn’t precise enough, pass an EmbedButtonPosition object with explicit pixel values.

Summary

PatternApproachBest For
1 — Manual*ngIf + EmbedButtonComponentExplicit imperative control
2 — Provider + screensEmbedProviderComponent + includeScreensRouter-based apps
3 — Object configConfig constant / service → EmbedProviderComponentShared config, env-driven
4 — Tab-basedVirtual paths from active tab stateNo-router / dashboard apps
5 — Visibility configembedButtonVisibilityConfig with groupsDelay + continuity policies
6 — startsWithmatchMode="startsWith"Nested route areas
7 — NgModuleEmbedModuleLegacy NgModule apps
8 — Global delayembedButtonDelayMsUniform delay, no group logic needed
Using EmbedProviderComponent + includeScreens is the recommended pattern for most Angular apps: one provider, one config list, the widget shown only on the screens you choose.