# EmbedProvider advanced (Angular) > Advanced EmbedProviderComponent patterns for Angular: route visibility, delay policies, group configuration, programmatic control, and best practices. URL: /embed/integration/angular/embed-angular-provider-advanced Markdown: /embed/integration/angular/embed-angular-provider-advanced.md # Angular — Advanced Provider Usage [#angular--advanced-provider-usage] > Advanced `EmbedProviderComponent` patterns — route-based visibility, delay policies, group configuration, programmatic control, and best practices. *** ## Table of Contents [#table-of-contents] 1. [Quick Start](#quick-start) 2. [EmbedProviderComponent — Props Reference](#embedprovidercomponent--props-reference) 3. [embedButtonVisibilityConfig](#embedbuttonvisibilityconfig) 4. [EmbedButtonComponent Props](#embedbuttoncomponent-props) 5. [EmbedInitService](#embedinitservice) 6. [Usage Patterns](#usage-patterns) 7. [Best Practices](#best-practices) *** ## Quick Start [#quick-start] ```typescript // app.component.ts import { Component, OnInit } from '@angular/core'; import { RouterModule } from '@angular/router'; import { EmbedInitService } from '@revrag-ai/embed-angular'; @Component({ selector: 'app-root', standalone: true, imports: [RouterModule], template: ``, }) export class AppComponent implements OnInit { constructor(private embedInit: EmbedInitService) {} ngOnInit(): void { this.embedInit.initialize('your_api_key'); } } ``` ```typescript // 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: ` `, }) 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 ``. 3. Keep `currentPath` in sync with the Angular router. 4. Add the CSS to `angular.json` styles array. *** ## EmbedProviderComponent — Props Reference [#embedprovidercomponent--props-reference] **Selector:** `revrag-embed-provider` | Input | Type | Default | Description | | ----------------------------- | ------------------------------------------ | ----------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `currentPath` | `string \| undefined` | `undefined` | **Highest-priority** path override. Keep in sync with the Angular router (or active tab). When omitted the widget is always hidden. | | `includeScreens` | `string[]` | `[]` | 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`). | | `embedButtonDelayMs` | `number` | `0` | Global delay (ms) before showing the button. Applies when no group config matches. | | `embedButtonVisibilityConfig` | `EmbedButtonVisibilityConfig \| undefined` | `undefined` | Per-group config for delays and continuity. See below. | | `embedButtonPosition` | `EmbedButtonPosition \| undefined` | `undefined` | Override the button's pixel position (`bottom`, `right`). | | `widgetPositioning` | `'fixed' \| 'embedded'` | `'fixed'` | `fixed` — viewport-fixed. `embedded` — in normal document flow. | | `widgetSide` | `'left' \| 'right' \| undefined` | `undefined` | Horizontal alignment of the widget. | | `widgetBottomOffset` | `number` | `0` | Extra pixels to raise the widget from the bottom. | | `widgetClassName` | `string` | `''` | Extra CSS class applied to the widget container. | ### Path detection [#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: ```typescript this.router.events .pipe(filter((e): e is NavigationEnd => e instanceof NavigationEnd)) .subscribe((e) => { this.currentPath = e.urlAfterRedirects; }); ``` *** ## embedButtonVisibilityConfig [#embedbuttonvisibilityconfig] Use this for per-route or per-group behavior — different delays, policies, and continuity settings. ```typescript 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', }, ], }; ``` ```html ``` ### EmbedButtonVisibilityConfig [#embedbuttonvisibilityconfig-1] | Field | Type | Description | | ---------------- | -------------------------- | --------------------------------------------------------- | | `defaultDelayMs` | `number` | Fallback delay when a group matches but has no `delayMs`. | | `groups` | `EmbedButtonGroupConfig[]` | Per-group configuration array. | ### EmbedButtonGroupConfig [#embedbuttongroupconfig] | Field | Type | Description | | ------------- | ----------------------------- | ------------------------------------------------------------- | | `id` | `string` | Unique ID for the group (used for session/continuity logic). | | `screens` | `string[]` | Paths belonging to this group. | | `continuity` | `'perScreen' \| 'continuous'` | See EmbedButtonContinuity below. | | `delayMs` | `number` | Delay (ms) before showing the button when this group matches. | | `delayPolicy` | `EmbedButtonDelayPolicy` | When to apply the delay. See below. | ### EmbedButtonContinuity [#embedbuttoncontinuity] | Value | Behavior | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `'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 [#embedbuttondelaypolicy] | Value | When 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 [#embedbuttoncomponent-props] Props for the standalone `` component (also forwarded internally by `EmbedProviderComponent`). | Input | Type | Default | Description | | -------------- | -------------------------------------------- | ----------- | ------------------------------------------------------------- | | `positioning` | `'fixed' \| 'embedded'` | `'fixed'` | `fixed` — viewport-fixed. `embedded` — in-flow. | | `side` | `'left' \| 'right' \| 'center' \| undefined` | `undefined` | Auto-position in bottom-left, bottom-right, or bottom-center. | | `bottomOffset` | `number` | `0` | Extra offset from bottom (e.g. for nav bars). | | `position` | `PositionConfig \| undefined` | `undefined` | Fine-grained CSS position override. | | `className` | `string` | `''` | Additional CSS class for the button container. | ### PositionConfig [#positionconfig] ```typescript interface PositionConfig { bottom?: string; // e.g. '24px' right?: string; // e.g. '24px' left?: string; top?: string; transform?: string; zIndex?: number; } ``` ### Example [#example] ```html ``` *** ## EmbedInitService [#embedinitservice] Initializes the SDK. Call **once** in `AppComponent.ngOnInit()` before any widget renders. ```typescript 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 [#sdkconfig-options] | Field | Type | Description | | ----------------- | ------------------------------- | ------------------------------------ | | `baseUrl` | `string` | Override API base URL. | | `enableTracker` | `boolean` | Enable analytics/tracking. | | `trackerCallback` | `(event: EventPayload) => void` | Custom callback for tracking events. | ### Return / Observables [#return--observables] | Observable / Getter | Type | Description | | ------------------- | -------------------------------- | ---------------------------------------------------- | | `isInitialized$` | `Observable` | Emits `true` after successful init. | | `isLoading$` | `Observable` | Emits `true` while init is in progress. | | `error$` | `Observable` | Emits error message if init fails, `null` otherwise. | | `sessionData$` | `Observable` | Stored session/config for debugging. | | `isInitialized` | `boolean` (getter) | Synchronous check. | *** ## Usage Patterns [#usage-patterns] ### Pattern 1 — Manual conditional rendering [#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. ```typescript 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: ` `, }) 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` [#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. ```typescript 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: ` `, }) 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"`: ```html ``` *** ### Pattern 3 — Object-based config [#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). ```typescript 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: ` `, }) 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 [#config-from-a-service-or-environment] ```typescript // 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`: ```typescript // environments/environment.ts export const environment = { embedApiKey: 'your_api_key', embedScreens: ['/offers', '/help', '/support'], }; ``` ```typescript // app.component.ts import { environment } from '../environments/environment'; ngOnInit(): void { this.embedInit.initialize(environment.embedApiKey); } ``` ```html ``` ```typescript embedScreens = environment.embedScreens; ``` *** ### Pattern 4 — Tab-based navigation (no router) [#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`. ```typescript 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: `
Dashboard content
Offers content — widget is visible here
Settings content
`, }) 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` [#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. ```typescript 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: ` `, }) 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 [#pattern-6--startswith-for-nested-routes] **Best for:** feature areas with nested routes where the widget should appear on any sub-page. ```typescript @Component({ template: ` `, }) 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 [#pattern-7--ngmodule-based-apps] For applications that have not migrated to standalone components, import `EmbedModule` in your root 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'; 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`) [#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: ```html ``` The widget will wait 3 seconds after any matching route becomes active before appearing. *** ## Best Practices [#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 [#summary] | Pattern | Approach | Best For | | -------------------------- | ---------------------------------------------------- | ------------------------------------ | | **1 — Manual** | `*ngIf` + `EmbedButtonComponent` | Explicit imperative control | | **2 — Provider + screens** | `EmbedProviderComponent` + `includeScreens` | Router-based apps | | **3 — Object config** | Config constant / service → `EmbedProviderComponent` | Shared config, env-driven | | **4 — Tab-based** | Virtual paths from active tab state | No-router / dashboard apps | | **5 — Visibility config** | `embedButtonVisibilityConfig` with groups | Delay + continuity policies | | **6 — startsWith** | `matchMode="startsWith"` | Nested route areas | | **7 — NgModule** | `EmbedModule` | Legacy NgModule apps | | **8 — Global delay** | `embedButtonDelayMs` | Uniform 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.