Skip to main content
Version: 5.2.0

Angular

Angular is one of the most popular web frontend frameworks at the moment. As such, it is extensively used by LightningChart JS users. Here you will find resources related to using LightningChart JS with Angular.

npm i @arction/lcjs

Basic usage boilerplate

Here's the Angular boilerplate code for creating perhaps the most common LightningChart JS use case - a Line Chart component.

ng generate component components/chart
  1. Prepare <div> element where LightningChart is placed
<!-- chart.component.html -->
<div id="chart"></div>
/* chart.component.css */
div {
width: 100%;
height: 40vh;
}
  1. Create and manage LightningChart JS components in Angular component
// chart.component.ts
export class ChartComponent implements OnDestroy {
private destroyLC?: () => unknown

constructor() {
// Only in browser (not server side);
afterNextRender(() => {
const container = document.getElementById('chart')
const lc = lightningChart()
const chart = lc.ChartXY({ container, theme: Themes.light })
const areaSeries = chart.addPointLineAreaSeries({ dataPattern: 'ProgressiveX' })
.appendSamples({ yValues: [0, 10, 8, 4, 7] })

this.destroyLC = () => {
lc.dispose()
}
}
}

ngOnDestroy(): void {
if (this.destroyLC) this.destroyLC()
}
}

This shows the minimal starting point for using LightningChart JS in Angular. Next examples cover more options around getting data to the charts.

Connecting to a data service (WebSocket example)

In Angular, functionality such as fetching data from a server is usually separated into logical, clear sections in code bases. A common concept is Service:

Service is a broad category encompassing any value, function, or feature that an application needs. A service is typically a class with a narrow, well-defined purpose. It should do something specific and do it well.

Next we'll walkthrough a simple example that covers:

  • Extremely minimalistic example WebSocket server that broadcasts random data points in real-time.
  • Creation of a Service that manages connections to above mentioned server.
  • Connecting the previous Chart component to this data service.

Here is the code of the example server (can also be found in GitHub):

import { WebSocketServer } from "ws";

const wss = new WebSocketServer({ port: 4201 });
wss.on("connection", async (ws) => {
let closed = false;
ws.onclose = () => {
closed = true;
};
while (!closed) {
ws.send(Math.random() * 11);
await new Promise((resolve) => setTimeout(resolve, 10));
}
});

Next, we setup the data service:

ng generate service services/data
// data.service.tsimport { Injectable } from '@angular/core';
import { Observable, Observer } from 'rxjs';

@Injectable({
providedIn: 'root',
})
export class DataService {
private observable?: Observable<number>;

constructor() {}

public connect(): Observable<number> {
if (!this.observable) {
this.observable = this.create();
}
return this.observable;
}

private create(): Observable<number> {
const ws = new WebSocket('ws://localhost:4201');
ws.onopen = () => {
console.log('WS connection successful');
};
const observable = new Observable((obs: Observer<number>) => {
ws.onmessage = (msg) => {
const value = Number(msg.data);
obs.next(value);
};
ws.onerror = obs.error.bind(obs);
ws.onclose = obs.complete.bind(obs);
return ws.close.bind(ws);
});
return observable;
}
}

Lastly, connecting the chart component to the data service, and setup a scrolling X axis:

// chart.component.ts
export class ChartComponent implements OnDestroy {
...
constructor(dataService: DataService) {
...
const axisX = chart
.getDefaultAxisX()
// Setup scrolling X axis
.setScrollStrategy(AxisScrollStrategies.progressive)
.setDefaultInterval((state) => ({
end: state.dataMax ?? 0,
start: (state.dataMax ?? 0) - 100,
stopAxisAfter: false,
}));
const areaSeries = chart
.addPointLineAreaSeries({ dataPattern: 'ProgressiveX' })
// Setup maximum amount of data points to keep in memory
.setMaxSampleCount(10_000);

const dataObservable = dataService.connect();
const dataSubscription = dataObservable.subscribe((value) => {
areaSeries.appendSample({ y: value });
});

this.destroyLC = () => {
chart.dispose();
dataSubscription.unsubscribe();
};
}
}

This wraps up a minimal example of connecting LC based Angular chart component to a real-time data service. The full example code can be found in GitHub.

If you consider use cases with static data (fetch data from server and show it), there is practically no difference. The logic of getting data is hidden behind the data service, and on component side only difference is that instead of pushing 1 sample at a time, you use appendSamples method to push many samples at once, and there is no need for a scrolling X axis.

Best practices for LightningChart JS based Angular components

Here you can find the most important best practices when developing Angular components using LightningChart JS.

Streaming data applications

LightningChart has extremely powerful APIs for appending new data points while keeping old data points around.

tip

For best results, new data points arriving should only result in Series.appendSamples or equivalent method being called, instead of clearing data / creating new series and re-defining full data set.

Optimizing apps with several charts visible at once / charts being initialized often

If your application involves having more than 1 chart visible at same time, or charts are frequently destroyed and recreated, then you should use a shared context for massive performance gains.

This can be done with a simple Service that manages a single LightningChart context and shares it between all LC-based components.

ng generate service services/lc-context
// lc-context.service.ts
import { Injectable } from '@angular/core';
import { LightningChart, lightningChart } from '@arction/lcjs';

@Injectable({
providedIn: 'root',
})
export class LcContextService {
private lc?: LightningChart;

constructor() {}

public getLightningChartContext() {
if (this.lc) return this.lc;
this.lc = lightningChart({
license:
// Place your LCJS license key here
// This should originate from a environment variable / secret. In development, license is assigned per developer, whereas in staging/production a deployment license is used.
'',
});
return this.lc;
}
}

Even if your application only has 1 chart visible at a time, it is still recommended to setup a service like this, because it provides a centralized place to manage LightningChart JS licensing in your code base.

// chart.component.ts
export class ChartComponent implements OnDestroy {
constructor(lcContextService: LcContextService) {
// Only in browser (not server side);
afterNextRender(() => {
const lc = lcContextService.getLightningChartContext();
...
// Cache function that destroys created LC resources
this.destroyLC = () => {
// NOTE: Important to dispose `chart` here, instead of `lc`. Otherwise we would dispose the shared LC context which may be used by other LC based components
chart.dispose();
};
}
}
}

Full example code can be found in GitHub.

Troubleshooting

Server side rendering

If your Angular application has server side rendering enabled, then you can run into unexpected issues if you try to invoke LightningChart JS functions in server side scripts.

The most common way to see this is "fp.atob is not a function" error message, or something similar.

There are a multitude of ways (provided by Angular itself) to ensure your code is running on browser instead of server. In our examples and documentation, we only utilized afterNextRender function (see above example snippets).

Runnable examples

For an example you can run and experiment with, see Angular template on GitHub. It covers same things that are described here.