Appearance
Angular
Angular uses the <chartbuddy-insight> custom element rather than a compiled Angular library. Angular binds properties and listens to events on custom elements natively, so you get the same lifecycle guarantees as the React and Vue bindings.
bash
npm install @chartbuddy.io/embedNo extra peer dependency — the element is plain DOM.
Why an element instead of an Angular package
A native Angular library has to be compiled with Angular's own toolchain and published against a specific Angular major, which would tie your ChartBuddy upgrades to your Angular upgrades. The element has no such coupling and works unchanged across Angular versions. If you would rather have typed Angular inputs, the thin wrapper below is about twenty lines and lives in your codebase, where it can follow your Angular version.
Setup
Import once — importing registers the element — and allow custom element tags in the components that use it.
ts
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import '@chartbuddy.io/embed/element';
@Component({
selector: 'app-revenue',
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<chartbuddy-insight
[chartData]="chartData"
instance-id="revenue"
style="display:block; height:400px"
(change)="onChange($event)"
></chartbuddy-insight>
`,
})
export class RevenueComponent {
chartData = {
chartType: 'clusteredBar',
isDataTransposed: true,
seriesData: [['', 'Q1', 'Q2'], ['Revenue', 100, 112]],
};
onChange(event: Event) {
const cd = (event as CustomEvent).detail;
// …
}
}For an NgModule app, put CUSTOM_ELEMENTS_SCHEMA in the module's schemas instead.
The chart fills its container, so give it a height.
Properties vs attributes
chartData is an object, so it must be set as a property — [chartData]="…" with square brackets. Angular sets DOM properties for bracket bindings on custom elements, so this works as written.
| Option | Binding |
|---|---|
chartData | [chartData]="cd" — property only |
instanceId | instance-id="revenue" or [instanceId]="id" |
editable | editable / [editable]="true" |
toolbar | toolbar / [toolbar]="flag" |
persist | [persist]="false" |
assetBase | asset-base="https://…/" |
Assigning a new object to chartData patches the live chart via update(). Changing any of the others remounts it. A burst of attribute changes in the same tick is coalesced into a single remount.
Mutation is not detected
chartData is compared by identity. With Angular's default change detection you must assign a new object:
ts
// Not picked up
this.chartData.title.text = 'FY26';
// Picked up
this.chartData = { ...this.chartData, title: { text: 'FY26' } };This also keeps you compatible with OnPush.
Events
All four are CustomEvents; read event.detail.
| Event | detail |
|---|---|
ready | The Insight instance |
change | ChartData | null |
mode | 'view' | 'edit' |
error | Error — a ChartDataValidationError for invalid config |
Note that error is a CustomEvent named error, not an ErrorEvent, and it does not bubble.
Imperative access
Grab the element with a template ref and read .insight:
ts
@ViewChild('chart') chartRef!: ElementRef<HTMLElement & { insight: any; ready: boolean }>;
async downloadPng() {
const { insight } = this.chartRef.nativeElement;
if (insight) await insight.downloadPng();
}html
<chartbuddy-insight #chart [chartData]="chartData" style="display:block; height:400px" />insight is null until the mount resolves; ready tells you when it has.
Optional: typed wrapper component
If you want Angular-native inputs and outputs, wrap the element once:
ts
import {
Component,
CUSTOM_ELEMENTS_SCHEMA,
EventEmitter,
Input,
Output,
} from '@angular/core';
import '@chartbuddy.io/embed/element';
import type { ChartData, ChartDataInput } from '@chartbuddy.io/embed';
@Component({
selector: 'cb-insight',
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<chartbuddy-insight
[chartData]="chartData"
[instanceId]="instanceId"
[editable]="editable"
style="display:block; height:100%"
(change)="change.emit($any($event).detail)"
(error)="error.emit($any($event).detail)"
></chartbuddy-insight>
`,
})
export class CbInsightComponent {
@Input() chartData?: ChartDataInput;
@Input() instanceId?: string;
@Input() editable = false;
@Output() change = new EventEmitter<ChartData | null>();
@Output() error = new EventEmitter<Error>();
}Then <cb-insight [chartData]="cd" (change)="save($event)" /> with no schema boilerplate at each call site.
Handling invalid data
Chart data is validated at the API boundary. Invalid config fires the error event instead of rendering a blank chart:
ts
import { ChartDataValidationError, validateChartData } from '@chartbuddy.io/embed';
onError(event: Event) {
const err = (event as CustomEvent).detail;
if (err instanceof ChartDataValidationError) {
this.issues = err.errors; // [{ path, code, expected, suggestion }, …]
}
}You can also check before binding, with no chart involved:
ts
const { valid, errors } = validateChartData(candidate);See Validation.
Dashboards
One element per chart, each with a stable instance-id. Duplicate ids on a page throw, so derive them from your data rather than the loop index.
html
@for (panel of panels; track panel.id) {
<chartbuddy-insight
[chartData]="panel.chartData"
[instanceId]="panel.id"
[persist]="false"
style="display:block; height:300px"
></chartbuddy-insight>
}The engine loads once per page, not once per chart — but read Installation before putting a lot of charts on one screen.
SSR
The element needs a real DOM, so don't import it on the server. Under Angular Universal, import it inside a browser-only guard:
ts
if (isPlatformBrowser(this.platformId)) {
await import('@chartbuddy.io/embed/element');
}There is no headless/Node render path today, so charts cannot be pre-rendered to PNG on a server.
Other frameworks
The same element works in Svelte, Solid, Lit, Astro, and plain HTML:
html
<script type="module">
import 'https://unpkg.com/@chartbuddy.io/embed/element.mjs';
const el = document.querySelector('chartbuddy-insight');
el.chartData = { chartType: 'pie', seriesData: [['Category', 'Value'], ['A', 3]] };
</script>
<chartbuddy-insight style="display:block; height:400px"></chartbuddy-insight>