Embedded Analytics in Angular: A Developer’s Guide (2026)
How to embed analytics and dashboards in an Angular app - SDKs, web components, CUSTOM_ELEMENTS_SCHEMA, guest tokens, row-level security, Angular Universal SSR, theming, and how Databrain, Sisense, Cube, Highcharts, and Power BI Embedded compare for Angular shops.
Key Takeaways
- Embedding analytics into an Angular app is a 1–5 day project; building the same capability from scratch is 2–6 months once you account for chart types, drill-downs, multi-tenancy, and exports.
- Three architectural patterns: iframes (zero integration, poor UX), JavaScript SDKs (tight Angular integration, bundle-size tax), and web components (Shadow DOM isolation, framework-agnostic). For customer-facing SaaS, SDK or web components win.
- Web components plug into Angular via
CUSTOM_ELEMENTS_SCHEMAon the host component - that single import unlocks the entire web-component ecosystem (Databrain, Embeddable, generic custom elements) without an Angular-specific SDK. - Never put your analytics vendor's API key on the frontend. Every production embed uses a short-lived guest token minted by your backend - and if you scope the token to a tenant ID, you get row-level security with minimal extra work (assuming your metric definitions filter on that field).
- Angular Universal (
@angular/ssr) needs the embed wrapped inisPlatformBrowser()or behind@defer (on idle)because custom elements register themselves against the globalwindowat import time. - On the embedded Angular SERP, Databrain, Sisense, Cube, Highcharts, and Power BI Embedded compete on different axes - pick based on whether you need a full dashboard UI, a headless semantic layer, a chart-only library, or the Microsoft stack.
Most SaaS products reach a point where customers ask for analytics - a dashboard inside the product where they can see their own data, filter it, drill in, and maybe export a CSV. Building that from scratch in Angular is a 2–6 month project once you're honest about chart types, drill-downs, multi-tenancy, and exports. Embedding a pre-built analytics layer is a 1–5 day project.
This guide covers what "embedded analytics in Angular" actually looks like in 2026: the three integration patterns (iframe, JS SDK, web component), what the Angular code looks like end-to-end, how authentication and row-level security work without building them yourself, the Angular-specific gotchas (CUSTOM_ELEMENTS_SCHEMA, SSR, change detection with custom elements), and an honest comparison of the platforms developers are actually using - Databrain, Sisense, Cube, Highcharts, Power BI Embedded.
If you'd rather build the whole thing from scratch, we have a separate guide: how to create an Angular dashboard. This article assumes you've decided to embed.
You'll learn:
- The three architectural patterns for embedding analytics in Angular (and which to avoid)
- How to install and configure an embed SDK or web component in a standalone Angular app
- Generating guest tokens from your Angular backend (NestJS or Express)
- Implementing row-level security via tokens (so tenant A can't see tenant B's data)
- Angular 19-specific concerns: standalone components,
CUSTOM_ELEMENTS_SCHEMA, change detection - Angular Universal SSR pitfalls and how to avoid them
- Theming to match your product
- How Databrain, Sisense, Cube, Highcharts, and Power BI Embedded compare for Angular shops
Finished code: github.com/databrainhq/dbn-demos-updated/tree/main/dbn-demo-angular - Angular CLI project with the web-component embed wired up end-to-end, including a Node backend that mints guest tokens.
When to Embed vs. Build From Scratch
Before the code, the honest decision:
| Approach | Time to Ship | Flexibility | Maintenance | Best For |
|---|---|---|---|---|
| Custom build | 2–6 months | Full control | You own it all | Unique UI, dashboard is core product differentiation |
| Free template (ngx-admin, CoreUI) | 2–3 days | Limited to template | Fork maintenance | Internal tools, admin panels |
| Component library (Syncfusion, Kendo, DevExtreme) | 1–2 weeks | Opinionated | Library updates | CRUD-heavy admin apps with many widgets |
| Embedded analytics (Databrain, Sisense, Cube, etc.) | 1–5 days | Configurable, vendor-bounded | Fully managed | Customer-facing dashboards in a SaaS product |
Build custom when:
- Your dashboard is the product (Mixpanel, Amplitude, Observable)
- Your UI has to do something no vendor supports (highly interactive simulations, custom workflows)
- You have a dedicated analytics team and a multi-year horizon
Embed when:
- Analytics is a feature inside a broader product, not the product itself
- You have multi-tenant data (each customer sees only their rows)
- You need more than 5–6 chart types
- You need exports, scheduled reports, drill-downs, and filter bars out of the box
- You want to ship this quarter, not next year
Most SaaS teams we've worked with started by building custom (or forking Apache Superset / Metabase OSS), then switched to embedding when the maintenance cost of 20+ chart types with drill-downs and cross-filtering became the breaking point. BerryBox, Freightify, and SpotDraft all went through this pattern - see their stories.
If you're still deciding, our embedded analytics build vs. buy guide goes into the full engineering-cost math (multi-tenancy and AI infrastructure are where most build estimates blow up). For the broader context on what embedded analytics means, see the complete embedded analytics guide. This article assumes you've decided to embed in an Angular app.
The Three Architectural Patterns
There are three ways analytics platforms integrate into an Angular app. It's worth understanding the difference because it affects bundle size, styling, auth, and how tightly you can customize.
1. iframes
The analytics platform hosts your dashboard; you embed it via <iframe src="...">.
- Pros: zero bundle-size impact on your app, full style isolation, trivial to implement
- Cons: poor interactivity with the parent page, awkward auth (cross-origin cookies + signed URLs), can't style beyond what the vendor exposes, resizing requires
postMessageplumbing - Who uses this: Grafana, older Power BI embeds, Tableau (classic mode), Looker
iframe embedding is fine for internal tools. For customer-facing SaaS, you usually want something better.
2. JavaScript / Angular SDKs
The vendor ships an npm package with Angular components or services. You import them, configure providers, and they render inline as part of your component tree.
- Pros: tight Angular integration, can compose with your app, prop-based customization, no cross-origin issues, works with Angular's change detection
- Cons: SDK becomes part of your bundle (often 500 KB+), styles may bleed or conflict with Material/your CSS, Angular version compatibility matters (the SDK may lag a major version behind)
- Who uses this: Power BI Embedded (
powerbi-client-angular), Sisense (Compose SDK has Angular bindings), Cube (uses the framework-agnostic JS SDK), Highcharts Angular (highcharts-angular), Tableau Embedding API (vanilla JS, wrap manually), Telerik ReportViewer
This is the most common approach for Angular shops in 2026, mostly because the existing vendors (Power BI, Tableau, Sisense) have invested in framework-specific bindings.
3. Web Components (Custom Elements)
The vendor ships HTML custom elements that work in any framework. You use them like <dbn-dashboard token="..." dashboard-id="..."></dbn-dashboard>. Under the hood they render inside Shadow DOM, which gives full style isolation without iframes.
- Pros: Shadow DOM isolates styles (no CSS conflicts ever), works in Angular, React, Vue, plain HTML, Svelte - one integration code path for all frameworks, smaller surface area than full SDKs
- Cons: Angular needs
CUSTOM_ELEMENTS_SCHEMAdeclared on the host component (one-line tax), property-bound inputs need[attr.foo]syntax for non-string values, Angular Universal SSR needs aisPlatformBrowser()guard - Who uses this: Databrain, Embeddable, some newer platforms
This is the approach we'll use in the code below. If your team uses more than one frontend framework, or if you're paranoid about CSS conflicts (Material's deeply nested theme cascade is a frequent culprit), web components are the cleanest option.
Step 1: Install the Embed Package
The code below uses @databrainhq/plugin as a concrete example because it's a web-component embed and shows the pattern cleanly. The integration shape - install, mint a token on the backend, render a component on the frontend, refresh the token before expiry - is near-identical for Sisense, Power BI Embedded, Cube, and Tableau. Swap the install command and the token endpoint for your vendor of choice; the structure transfers.
@databrainhq/plugin ships <dbn-dashboard> and <dbn-metric> as web components:
npm install @databrainhq/pluginAngular 19 standalone note: there's no
app.module.tsto register custom elements in. Instead, declareCUSTOM_ELEMENTS_SCHEMAon each standalone component that uses the embed (or on a parent that wraps it). The web-component registration itself happens on import - so you only import@databrainhq/plugin/webonce, inmain.ts.
Step 2: Register the Custom Elements
Register the web components once at app bootstrap, in main.ts:
// src/main.ts
import '@databrainhq/plugin/web';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));The import '@databrainhq/plugin/web' line registers <dbn-dashboard> and <dbn-metric> against customElements globally. From this point, any Angular template anywhere in your app can render them - provided that template's component declares CUSTOM_ELEMENTS_SCHEMA.
Step 3: Generate a Guest Token from Your Backend
Never put your API key on the frontend. Every embed SDK works on the same principle: your backend exchanges your long-lived API key for a short-lived "guest token" scoped to a specific user. The frontend only ever sees the guest token.
Here's the token endpoint in NestJS (the most common Angular-shop backend):
// src/analytics/analytics.controller.ts
import { Body, Controller, HttpException, Post, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { CurrentUser } from '../auth/current-user.decorator';
@Controller('api/guest-token')
@UseGuards(AuthGuard('jwt'))
export class AnalyticsController {
@Post()
async createToken(@CurrentUser() user: { tenantId: string }) {
const res = await fetch(
`${process.env.DATABRAIN_API_URL}/api/v2/guest-token/create`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.DATABRAIN_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
clientId: user.tenantId,
dataAppName: process.env.DATABRAIN_DATA_APP_NAME,
permissions: {
isEnableManageMetrics: false,
isEnableCustomizeLayout: false,
isEnableUnderlyingData: false,
isEnableDownloadMetrics: true,
isShowDashboardName: true,
},
}),
},
);
const data = await res.json();
if (!res.ok || !data.token) {
throw new HttpException(data?.error?.message ?? 'Token creation failed', 400);
}
return { token: data.token };
}
}Or in Express, if your Angular app talks to a Node/Express backend:
// server/routes/guest-token.js
import express from 'express';
const router = express.Router();
router.post('/api/guest-token', authenticate, async (req, res) => {
const response = await fetch(
`${process.env.DATABRAIN_API_URL}/api/v2/guest-token/create`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.DATABRAIN_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
clientId: req.user.tenantId,
dataAppName: process.env.DATABRAIN_DATA_APP_NAME,
}),
},
);
const data = await response.json();
if (!response.ok || !data.token) {
return res.status(400).json({ error: data?.error?.message ?? 'Token creation failed' });
}
res.json({ token: data.token });
});
export default router;Two things to notice:
clientIdis the tenant boundary. Whatever you pass asclientIddetermines what data the token can see. In a SaaS app, derive it from the authenticated user's session (req.user.tenantId), never from the frontend payload - and make sure every metric definition in your dashboard filters on this field. Miss it in one metric and you've shipped a cross-tenant leak, so treat metric definitions with the same rigor as parameterized SQL in your main app.- The
permissionsobject is your embed RBAC. Different user roles (viewer, editor, admin) get different guest tokens with different permissions. You still need your own auth to decide which user gets which token - but you don't build a separate permission-check system for the analytics layer.
Step 4: The Angular Component
Now the frontend side - fetch the token, render the dashboard, refresh the token before it expires (guest tokens typically last 1 hour). This is a standalone component with CUSTOM_ELEMENTS_SCHEMA.
// src/app/features/analytics/analytics-dashboard.component.ts
import {
ChangeDetectionStrategy,
Component,
CUSTOM_ELEMENTS_SCHEMA,
inject,
Input,
OnDestroy,
OnInit,
signal,
} from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
@Component({
selector: 'app-analytics-dashboard',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
@if (token(); as t) {
<dbn-dashboard
[attr.token]="t"
[attr.dashboard-id]="dashboardId"
[attr.enable-download-csv]="true"
[attr.enable-email-csv]="true"
></dbn-dashboard>
} @else {
<div class="loading">Loading analytics…</div>
}
`,
styles: [
`
:host { display: block; }
dbn-dashboard { display: block; width: 100%; min-height: 600px; }
.loading { padding: 40px; text-align: center; color: #64748b; }
`,
],
})
export class AnalyticsDashboardComponent implements OnInit, OnDestroy {
private readonly http = inject(HttpClient);
@Input({ required: true }) dashboardId!: string;
protected readonly token = signal<string | null>(null);
private intervalId: ReturnType<typeof setInterval> | null = null;
async ngOnInit() {
await this.refreshToken();
// Refresh 10 minutes before the 1-hour token expiry.
this.intervalId = setInterval(() => this.refreshToken(), 50 * 60 * 1000);
}
ngOnDestroy() {
if (this.intervalId) clearInterval(this.intervalId);
}
private async refreshToken() {
try {
const res = await firstValueFrom(
this.http.post<{ token: string }>('/api/guest-token', {}),
);
this.token.set(res.token);
} catch (err) {
console.error('Failed to refresh embed token', err);
}
}
}A few Angular-specific details worth flagging:
schemas: [CUSTOM_ELEMENTS_SCHEMA]tells Angular's template compiler "trust me,<dbn-dashboard>is a real element". Without it you'll get'dbn-dashboard' is not a known elementat build time.[attr.token]="t"not[token]="t"- Angular's property binding[token]only works for properties declared on a component class. For arbitrary custom elements, attribute binding ([attr.foo]) is what reaches the DOM.- Boolean attributes like
enable-download-csvstill need the truthy value passed as a string-coerced attribute - Angular renderstrueas the string"true", which Custom Element implementations typically interpret correctly. OnPushis fine - the embed renders inside Shadow DOM and manages its own change detection; nothing inside the dashboard triggers Angular's CD.
That's the whole embed. The <dbn-dashboard> element handles chart rendering, drill-downs, cross-filters, CSV export, email export, theming, and real-time data - all configured in the dashboard's definition in Databrain, not in your Angular code.
Use it the same way you'd use any standalone component:
// src/app/features/reports/reports.page.ts
import { Component, inject } from '@angular/core';
import { AnalyticsDashboardComponent } from '../analytics/analytics-dashboard.component';
import { AuthStore } from '@core/auth.store';
@Component({
selector: 'app-reports',
standalone: true,
imports: [AnalyticsDashboardComponent],
template: `
<h1>Your Reports</h1>
<app-analytics-dashboard dashboardId="revenue-overview" />
`,
})
export class ReportsPage {
protected readonly auth = inject(AuthStore);
}Step 5: Row-Level Security via Guest Tokens
Row-level security is the hardest thing to build from scratch, and the biggest reason teams switch from custom to embedded. Here's why.
In a multi-tenant SaaS, tenant A must never see tenant B's rows, even if they know each other's URLs. Building this yourself in an Angular + NestJS stack requires:
- A tenant-aware
Guardand / orInterceptoron every API route - WHERE clauses injected into every TypeORM / Prisma query (or Postgres RLS policies, if you're careful)
- Audit logs for access attempts
- UI that hides/shows features based on tenant plan
- Integration tests for every tenant-scoping bug you'll create
With embed SDKs, this is reduced to passing the right clientId when minting the token. The token itself carries the scope:
const token = await createGuestToken({
clientId: req.user.tenantId,
});The analytics platform resolves every query for that token with a WHERE tenant_id = $clientId filter - as long as every metric definition in your dashboard references that field. If one metric skips the filter, that metric leaks across tenants. The platform reduces the surface area a lot, but it doesn't eliminate the need to review metric definitions when you add new ones.
This is also how you do user-level access on top of tenant-level access. Two patterns:
- Soft filtering via token context: pass extra context like
{ userId, role }in the token and have your metric definitions filter on it - Permission flags in the token: the
permissionsobject in the token body controls what features show up (metric creation, layout customization, downloads)
The shorthand: you define your row-scoping rules once in the platform, and every embed for every customer honors them. No leaky middleware, no forgotten WHERE clause.
Step 6: Angular Universal (SSR) Considerations
If your Angular app uses @angular/ssr, the embed needs special handling. Custom elements register themselves against window.customElements at import time - there's no window on the server, so the import will crash the SSR build.
Two patterns that work:
Pattern A: Browser-only import + isPlatformBrowser() guard
Move the import '@databrainhq/plugin/web' out of main.ts and into a method that only runs in the browser:
import {
Component,
CUSTOM_ELEMENTS_SCHEMA,
inject,
PLATFORM_ID,
OnInit,
signal,
} from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
@Component({
selector: 'app-analytics-dashboard',
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
@if (ready() && token()) {
<dbn-dashboard [attr.token]="token()" [attr.dashboard-id]="dashboardId" />
}
`,
})
export class AnalyticsDashboardComponent implements OnInit {
private readonly platformId = inject(PLATFORM_ID);
protected readonly ready = signal(false);
protected readonly token = signal<string | null>(null);
async ngOnInit() {
if (!isPlatformBrowser(this.platformId)) return;
await import('@databrainhq/plugin/web');
this.ready.set(true);
// ... fetch token as before
}
}The dynamic import only runs in the browser, so the SSR pre-render produces an empty placeholder. Hydration on the client kicks in, the import resolves, and the embed mounts.
Pattern B: @defer (on idle)
Even simpler - wrap the embed in a @defer block. The Angular compiler will produce a separate bundle for the embed, and that bundle won't load on the server:
@defer (on idle) {
<app-analytics-dashboard [dashboardId]="dashboardId" />
} @placeholder {
<div class="placeholder">Loading analytics…</div>
}@defer (on idle) waits for the browser's idle callback, which never fires on the server, so the embed never renders during SSR. Cleaner than the manual isPlatformBrowser dance for most cases.
What you can't do during SSR
- Pre-render the dashboard's content. The embed is rendered by the vendor's runtime, which lives in JavaScript. There's no SSR-compatible "render this dashboard to HTML" path. SSR for embeds is always "render an empty container, hydrate on the client".
- SEO-index the dashboard data. If your embedded dashboard is on a public marketing page (rare), Google won't see the chart contents. Use plain HTML for the SEO-meaningful version and the embed for the interactive version.
For most authenticated dashboards, this isn't a problem - there's nothing to SEO-index inside a logged-in app.
Step 7: Theming to Match Your Brand
Embedded dashboards that don't match your product's look feel like a bolted-on iframe, and users notice. Pull theme tokens from your design system (CSS variables, Tailwind config, or Material's theme tokens) so brand refreshes don't require redeploying the analytics integration:
<dbn-dashboard
[attr.token]="token()"
[attr.dashboard-id]="dashboardId"
[attr.theme]="themeJson"
[attr.options]="optionsJson"
></dbn-dashboard>protected readonly themeJson = JSON.stringify({
button: {
primary: 'var(--mat-sys-primary)',
primaryText: 'var(--mat-sys-on-primary)',
},
drillBreadCrumbs: {
fontFamily: 'Inter, sans-serif',
fontColor: '#374151',
activeColor: '#2563eb',
},
datePickerColor: '#0066CC',
});
protected readonly optionsJson = JSON.stringify({
chartColors: ['#0066CC', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6'],
});A few tips from teams who've shipped this:
- Pull theme tokens from your Material theme (
var(--mat-sys-primary)) rather than hardcoding hex codes - that way Material theme changes propagate to the embed automatically. - Chart palettes matter more than you think. Default rainbow palettes look generic; a 5–7 color palette derived from your brand makes the embed look native.
- **Shadow DOM means your global Material CSS can't leak in either.** This is normally a feature, but it means you can't override the embed's internals with global SCSS - use the theming API.
Full theming API (chart palettes, breakpoints, metric layouts, font scaling) is documented in the component API reference.
How Embedded Analytics Platforms for Angular Compare
The Angular embedded analytics SERP is shaped by the historical strength of the Microsoft + Java enterprise stack - Power BI, Tableau, and Sisense all have first-class Angular bindings that React doesn't get. Honest breakdown:
| Platform | Integration | Strength | Weakness | Starts at |
|---|---|---|---|---|
| Databrain | Web components (<dbn-dashboard>) | Full analytics app with 48 chart types, drill-downs, RLS, exports, scheduled reports, multi-framework via web components | Commercial-only - no free tier or OSS option for evaluation; smaller OSS community than Sisense or Power BI | $999–$1,995/mo, unlimited viewers |
| Power BI Embedded | powerbi-client-angular | Mature, deep Microsoft integration, included in many enterprise Office 365 plans, Angular-native bindings | Tied to Azure AD; styling is highly constrained; per-capacity pricing gets expensive at scale; row-level security is configured in Power BI itself, not in your Angular code | A1 capacity ~$735/mo |
| Sisense Compose SDK | Angular library + components | Granular React/Angular components, Vue support, good drill-down API, mature product | Compose SDK is newer and Angular bindings lag React; full deployment requires Sisense server, which is a large commitment | Contact sales |
| Tableau Embedding API v3 | Vanilla JS web component (<tableau-viz>) | Tableau's chart library, very wide adoption, rich interactivity | Requires Tableau Server / Cloud, which is significant infrastructure; styling is limited | $15+/user/mo (embed pricing varies) |
| Cube (with custom Angular UI) | Cube REST/GraphQL API + your own charts | Best if you've adopted Cube as your semantic layer; headless | No dashboard UI out of the box - you build the charts in Angular | Free (OSS) / $300+/mo (cloud) |
| Highcharts Angular | highcharts-angular | Mature, 30+ chart types, boost mode for huge datasets, Angular-native bindings | A chart library, not a dashboard - you still build the surrounding dashboard yourself | Commercial license required for paid use |
| Embeddable | Web components | Developer-focused, code-based data models, framework-agnostic | Newer product, smaller community, UI customization is code-first | Contact sales |
Pricing disclaimer: starts-at numbers are as of April 2026 and move frequently - always cross-check with each vendor's current pricing page before relying on these for a build-vs-buy decision.
Quick picking guide:
- If you want the fastest path to a multi-tenant customer-facing dashboard with many chart types, drill-downs, and exports: Databrain
- If you're already deep in the Microsoft / Azure AD stack: Power BI Embedded
- If you have a Sisense investment and need Angular bindings: Sisense Compose SDK
- If you want a headless semantic layer and are happy to build chart UI yourself: Cube
- If you want zero frontend assumptions and maximum style isolation: Databrain or Embeddable (both use web components - same pattern in Angular, React, and Vue)
- If you only need charts, not a full dashboard: Highcharts Angular or ng2-charts
For a direct comparison on specific competitors, see our Power BI Embedded alternatives guide.
What You Get Out of the Box vs. Building It
If you skipped the custom Angular dashboard guide because you've already decided to embed, here's the concrete feature gap you're not rebuilding:
| Capability | Custom Build Cost | Embedded Cost |
|---|---|---|
| Chart types | ~1 week per new type past the basics (ng2-charts covers ~8) | 30–48 built-in (varies by vendor) |
| Drill-downs with cross-filtering | 2–4 weeks | Declarative config |
| Data connectors | 1 week per source (Postgres, Snowflake, BigQuery, ClickHouse…) | 15–20 built-in |
| Multi-tenancy / row-level security | 2–4 weeks + a permanent maintenance tax | Token-scoped RLS |
| CSV / PDF export | 1–2 weeks + server-side rendering for PDF | Built-in |
| Scheduled email reports | 2–3 weeks (queue, templating, deliverability) | Built-in |
| Dashboard builder UI | Rarely worth building - multi-quarter project | Included (for admins) |
| Shadow DOM style isolation | Angular's ViewEncapsulation.ShadowDom (with caveats) | Free via web components |
| SSR-safe embed | Not applicable | @defer (on idle) or isPlatformBrowser() guard |
| Time to production | 2–6 months | 1–5 days |
The interesting column isn't time-to-ship - it's maintenance tax. Every one of those capabilities needs ongoing engineering forever. Embedded analytics moves that off your roadmap.
When to Build Anyway
Embedding isn't always right. Build custom when:
- The dashboard is the product. If you're Mixpanel, you don't embed Sisense.
- You need UI that no vendor exposes. Interactive simulations, custom drawing tools, dashboards with real-time collaboration cursors.
- You have an analytics team. If you have three engineers whose full-time job is dashboards, building gives them leverage.
- The data is ultra-sensitive and can't leave your VPC. Many embedded platforms offer self-hosted deployment on Docker/Kubernetes/VMs, but if your compliance posture forbids any vendor anywhere in the data path, build.
If any of those describe you, start with our guide on creating an Angular dashboard from scratch - the code is in a runnable starter at github.com/databrainhq/dbn-demos-updated/tree/main/angular-tutorial-scratch. And before you commit, work through the build vs. buy cost breakdown - the multi-tenancy and AI-infrastructure cost lines are where homegrown estimates consistently miss.
For everyone else, embed. The labor math is rarely close.
Next Steps
- Clone a working demo: dbn-demo-angular - Angular CLI project with the web-component embed and Node backend wired up end-to-end
- If you still want to build from scratch: How to create an Angular dashboard - a full Angular 19 + Material + ng2-charts + Signals tutorial, with a runnable starter repo
- The full build vs. buy math: Embedded analytics build vs. buy - the multi-tenancy and AI costs most teams miss
- What is embedded analytics? Complete embedded analytics guide - architecture, buyer's checklist, vendor landscape
- See how teams got here: BerryBox tried Power BI, switched to Databrain, saved $250K and 6 months of engineering. Freightify built in-house, then swapped to Databrain in a week. SpotDraft replaced Looker in 4 weeks and saved $300K plus 9 months of engineering.
- Try it free: Start building with Databrain
- Developer docs: docs.usedatabrain.com
Covers Angular 19, @angular/ssr, @databrainhq/plugin v0.16. Last updated April 2026.
Rahul Pattamatta is co-founder of Databrain, an embedded analytics platform for SaaS.
Frequently Asked Questions
What's the difference between an iframe embed and a web-component embed in Angular?
Iframes are zero bundle impact and zero integration, but styling and auth are awkward - cross-origin cookies, postMessage plumbing for resizing, no shared change detection. Web components plug into Angular templates via CUSTOM_ELEMENTS_SCHEMA and render inside Shadow DOM, giving full style isolation without the iframe tax. For customer-facing SaaS, web components beat iframes almost every time.
How do I authenticate an embedded dashboard in Angular?
Never put your analytics platform's API key on the frontend. The pattern is: authenticated user hits your Angular backend (Express, NestJS, or whatever), the backend exchanges its API key for a short-lived "guest token" scoped to that user (often an hour), the frontend receives only the guest token, the embed component uses the token to render. Refresh the token before expiry with a 50-minute setInterval inside ngOnInit, and clean up in ngOnDestroy.
What is `CUSTOM_ELEMENTS_SCHEMA` and when do I need it?
CUSTOM_ELEMENTS_SCHEMA is an Angular schema that tells the template compiler to allow unknown HTML elements (like <dbn-dashboard>). You declare it in the schemas array of any standalone component that uses a web component. Without it, Angular's strict template type-checking throws 'dbn-dashboard' is not a known element at build time. It only affects template compilation - it doesn't change runtime behavior.
Can I embed analytics in a server-side-rendered Angular app (Angular Universal / `@angular/ssr`)?
Yes, but the embed has to skip server-side rendering. Custom elements register against window.customElements at import time, so importing the embed package on the server crashes SSR. Two safe patterns: (1) wrap the embed in a @defer (on idle) block - the bundle never loads on the server, or (2) use isPlatformBrowser() to guard a dynamic import() of the embed package. Either way, the dashboard renders only on the client after hydration.
How do I implement row-level security without building it myself?
Every major embed SDK resolves queries in the context of the token - so if the token is scoped to tenantId = 42, every query for that token gets a WHERE tenant_id = 42 filter, provided the metric definition references that field. Configure this once per metric in the dashboard, review every new metric for the same filter, and the platform handles the rest. You still own the discipline of getting metrics right; you don't own the middleware plumbing.




