--- url: https://chartbuddy.io/embed/docs/guides/angular.md --- > Docs index: [llms.txt](https://chartbuddy.io/embed/docs/llms.txt) # Angular Angular uses the `` **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/embed ``` No 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](#optional-typed-wrapper-component) 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: ` `, }) 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"` | | `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 `CustomEvent`s; 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; async downloadPng() { const { insight } = this.chartRef.nativeElement; if (insight) await insight.downloadPng(); } ``` ```html ``` `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: ` `, }) export class CbInsightComponent { @Input() chartData?: ChartDataInput; @Input() instanceId?: string; @Input() editable = false; @Output() change = new EventEmitter(); @Output() error = new EventEmitter(); } ``` Then `` 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](https://chartbuddy.io/embed/docs/quality-assurance/validation.md). ## 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) { } ``` The engine loads once per page, not once per chart — but read [Installation](https://chartbuddy.io/embed/docs/getting-started/installation.md#bundle-size-and-many-charts) 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 ```