# 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.