1

I've developed a custom element using Lit, incorporating the Chart.js library. This element exposes properties like data, options, plugins, and type, which are used to generate the chart. When consuming my custom chart element within an Angular framework and registering chartjs-plugin-datalabels inline, the data labels initially flicker and are not positioned correctly as per the config i passed inside the plugins, which inturn is inside the options. However, if I import the plugin and perform a global registry inside my custom chart component, the issue is resolved.

For example, in my Angular framework:

<sk-chart [data]="data" [options]="options" type="bar" [plugins]="dataLabels"></sk-chart>

in .ts:

import chartDataLabels from 'chartjs-plugin-datalabels';
dataLabels = [chartDataLabels];
datalabels: {
  color: '#000',
  anchor: 'end',
  align: 'end',
  offset: 4,
  font: {
    weight: 'bold'
  }
}

I want the data labels to remain positioned at the top of the bar as per the datalabelk config I pass. Initially, they appear at the top correctly but then suddenly shift down and shifts bit right. This issue occurs in both React and Angular but does not appear when consuming my element in Storybook or Next.js or just plain vanilla js. The problem only resolves when I perform a global registration inside (sk-chart.ts), as shown below:

import chartDataLabels from 'chartjs-plugin-datalabels';
Chart.register(chartDataLabels);

But I want to achieve it via inline registry

Does anyone have insights into why this happens specifically in React and Angular?

This is how it looks in angular framework:

datalabels in angular application

1 Answer 1

0

It sounds like what you're encountering is due to Chart.js plugin registration lifecycle differences across environments, especially when using frameworks like Angular and React, which render components asynchronously and sometimes with timing delays.

Why this happens:

When you register the plugin inline like :

<sk-chart [data]="data" [options]="options" type="bar" [plugins]="dataLabels"></sk-chart>

...the plugin is passed to the chart instance just-in-time. However, in Angular/React, rendering and DOM lifecycles can delay the rendering or make it non-deterministic in relation to Lit's internal lifecycle (firstUpdated, updated, etc.), especially when nested inside custom elements.

So what happens is:

  • Chart is initialized with dataLabels plugin
  • Plugin is not fully applied before layouting begins (or is applied too late)
  • Labels flicker or are rendered incorrectly on first render
  • Works perfectly only after a re-render or full plugin registration

In contrast, when you do:

Chart.register(chartDataLabels);

You're making the plugin globally availableto all Chart.js instances before they are initialized, which guarantess correct behavior regardless of the framework timing.

Why Vanilla JS or Storybook Works:

These environments don’t introduce additional component lifecycles or asynchronous rendering — so your Lit element has full control over timing. Hence, inline plugin works predictably.

Workarounds (without global registration):

  1. Delay chart rendering until after the component is fully connected and stable:

In your Lit component, add something like this in firstUpdated:

requestAnimationFrame(() => {
  this.renderChart(); // or recreate it manually
});
  1. Force chart re-creation after a short delay:
setTimeout(() => this.renderChart(), 0); // Let Angular finish DOM patching
  1. Use updateComplete from Lit:
this.updateComplete.then(() => {
  this.renderChart();
});
  1. In Angular, ensure bindings are stable before rendering the chart. You can also wrap <sk-schart> in *ngIf that flips true after a ```setTimeout to mimic a post-render registration.

Recommendation:

If global registration is acceptable in your architecture, it's the most stable solution. Otherwise, defer chart rendering using updateComplete, requestAnimationFrame, or a setTimeout to fix flickering and misalignment when using inline plugins.

Sample Code

GOAL

  • Use chartjs-plugin-datalabels via inline registration
  • Prevent label misalignment or flickering in Angular/React
  • No global Chart.register(...) inside your component

Setup Assumptions

  • You have Chart.js and chartjs-plugin-datalabels installed
  • You are embedding your Lit component (sk-chart) in an Angular or React app

Lit Component : sk-chart.ts

import { LitElement, html, css, property } from 'lit';
import { customElement } from 'lit/decorators.js';
import Chart from 'chart.js/auto';

@customElement('sk-chart')
export class SkChart extends LitElement {
  @property({ type: Object }) data = {};
  @property({ type: Object }) options = {};
  @property({ type: String }) type = 'bar';
  @property({ type: Array }) plugins: any[] = [];

  private chart: Chart | null = null;

  static styles = css`
    canvas {
      width: 100%;
      height: auto;
    }
  `;

  firstUpdated() {
    // Delay rendering chart until after the DOM + plugins are fully ready
    this.updateComplete.then(() => {
      requestAnimationFrame(() => this.renderChart());
    });
  }

  updated(changedProps: Map<string, unknown>) {
    if (
      changedProps.has('data') ||
      changedProps.has('options') ||
      changedProps.has('type') ||
      changedProps.has('plugins')
    ) {
      this.updateComplete.then(() => {
        requestAnimationFrame(() => this.renderChart());
      });
    }
  }

  renderChart() {
    const canvas = this.shadowRoot?.getElementById('chart') as HTMLCanvasElement;

    if (this.chart) {
      this.chart.destroy();
    }

    this.chart = new Chart(canvas, {
      type: this.type,
      data: this.data,
      options: this.options,
      plugins: this.plugins || [],
    });
  }

  render() {
    return html`<canvas id="chart"></canvas>`;
  }
}

Angular Usage Example :

app.component.ts

import { Component } from '@angular/core';
import chartDataLabels from 'chartjs-plugin-datalabels';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  data = {
    labels: ['Jan', 'Feb', 'Mar'],
    datasets: [
      {
        label: 'Sales',
        data: [100, 200, 300],
        backgroundColor: '#2196f3'
      }
    ]
  };

  options = {
    plugins: {
      datalabels: {
        color: '#000',
        anchor: 'end',
        align: 'end',
        offset: 4,
        font: {
          weight: 'bold'
        }
      }
    }
  };

  type = 'bar';
  plugins = [chartDataLabels];
}

app.component.html

<sk-chart
  [data]="data"
  [options]="options"
  [type]="type"
  [plugins]="plugins">
</sk-chart>

Result

  • No need for Chart.register(chartDataLabels)
  • Data labels render correctly at the start (no shift)
  • Works smoothly inside Angular and React
  • Component remains reusable and framework-agnostic
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.