Using Chart.js on Angular 4

I'm trying to use Chart.js with Angular 4, I saw an example on the chart.js documents but it's using a < script > tag to pull the script so it doesn't work on the component. This is how I tried to fit into Angular:

TS

export class GraphicsComponent implements OnInit {

  ctx = document.getElementById("myChart");
  myChart = new Chart(this.ctx, {
    type: 'pie',
    data: {
        labels: ["New", "In Progress", "On Hold"],
        datasets: [{
            label: '# of Votes',
            data: [1,2,3],
            backgroundColor: [
                'rgba(255, 99, 132, 1)',
                'rgba(54, 162, 235, 1)',
                'rgba(255, 206, 86, 1)'
            ],
            borderWidth: 1
        }]
    },
    options: {
      responsive: false,
      display:true
    }
  });

  constructor() { }

  ngOnInit() {
  }

}

HTML

<canvas id="myChart" width="700" height="400"></canvas>

Any idea how I would make it work?

EDIT: I have updated my code with the import and I'm now receiving an error.

Import Code:

import * as Chart from 'chart.js'

Error on chrome console:

ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'length' of null
TypeError: Cannot read property 'length' of null
    at Object.acquireContext (platform.dom.js:333)
    at Chart.construct (core.controller.js:74)
    at new Chart (core.js:42)
    at new GraphicsComponent (graphics.component.ts:12)
    at createClass (core.es5.js:10922)
    at createDirectiveInstance (core.es5.js:10756)
    at createViewNodes (core.es5.js:12197)
    at createRootView (core.es5.js:12092)
    at callWithDebugContext (core.es5.js:13475)
    at Object.debugCreateRootView [as createRootView] (core.es5.js:12792)
    at Object.acquireContext (platform.dom.js:333)
    at Chart.construct (core.controller.js:74)
    at new Chart (core.js:42)
    at new GraphicsComponent (graphics.component.ts:12)
    at createClass (core.es5.js:10922)
    at createDirectiveInstance (core.es5.js:10756)
    at createViewNodes (core.es5.js:12197)
    at createRootView (core.es5.js:12092)
    at callWithDebugContext (core.es5.js:13475)
    at Object.debugCreateRootView [as createRootView] (core.es5.js:12792)
    at resolvePromise (zone.js:795)
    at resolvePromise (zone.js:766)
    at zone.js:844
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:425)
    at Object.onInvokeTask (core.es5.js:3881)
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:424)
    at Zone.webpackJsonp.../../../../zone.js/dist/zone.js.Zone.runTask (zone.js:192)
    at drainMicroTaskQueue (zone.js:602)
    at <anonymous>

Answers:

Answer

You can install the package, along with the types for full functionality in a typescript environment such as Angular:

npm install --save chart.js && npm install --save-dev @types/chart.js

Then in your component you can now import * as Chart from 'chart.js' and use it in your typescript environment. Check out this example for implementation methods using typescript.

Because you need to get the canvas from the DOM you need to make sure it's rendered before attempting to access it. This can be accomplished with AfterViewInit.

import { Component, AfterViewInit } from '@angular/core';
import * as Chart from 'chart.js'

export class MyChartComponent implements AfterViewInit {

  canvas: any;
  ctx: any;

  ngAfterViewInit() {
    this.canvas = document.getElementById('myChart');
    this.ctx = this.canvas.getContext('2d');
    let myChart = new Chart(this.ctx, {
      type: 'pie',
      data: {
          labels: ["New", "In Progress", "On Hold"],
          datasets: [{
              label: '# of Votes',
              data: [1,2,3],
              backgroundColor: [
                  'rgba(255, 99, 132, 1)',
                  'rgba(54, 162, 235, 1)',
                  'rgba(255, 206, 86, 1)'
              ],
              borderWidth: 1
          }]
      },
      options: {}
    });
  }

}
Answer

In my Angular 6.1 site, I am using chart.js by itself in mgechev/angular-seed.

As of writing this response, Chart.js developers have some work to do to make exporting its members compliant with newer standards so rollups may be problematic.

To get Chart.js 2.7.x to work after installing package chart.js and types @types/chart.js in this angular-seed, all I needed to do is:

  1. Update project.config.ts to include ROLLUP_NAMED_EXPORTS to get rollup to work properly (if you're using a rollup).

    this.ROLLUP_NAMED_EXPORTS = [
      ...this.ROLLUP_NAMED_EXPORTS,
      { 'node_modules/chart.js/src/chart.js': ['Chart'] }
    ];
    
  2. Update project.config.ts to include additional packages. This seed uses SystemJS config. This might vary if you're using something else.

    // Add packages
    const additionalPackages: ExtendPackages[] = [
     { name: 'chart.js', path: 'node_modules/chart.js/dist/Chart.bundle.min.js' },
    
      ...
    
    ];
    
  3. In your component

    import { Chart } from 'chart.js';
    
    ...
    
    export class MyComponent implements OnInit {
    
    @ViewChild('myChart') myChartRef: ElementRef;
    chartObj: Chart;
    
    ...
    }
    

    Then load chart configuration in ngOnInit() per Chart.js documentation

  4. HTML will look something like this:

    <div class="chart-container">
       <canvas #myChart></canvas>
    </div>
    
Answer

So, after a lot of thought, I decided to jump in on this thread, b/c there is an Angular way of doing this that follows best practices and doesn't use any document scope querying... (yuck). For brevity's sake, I've used a template in the Component decorator, not a templateUrl, but they behave the same as if it were in a template. The DOM still has to e loaded first, and that's the big gotcha with Chart.js. This example is for Angular 8+ and is working with my current version which is 9.1.7

import { Component, ViewChild, AfterViewInit, Input } from '@angular/core'
import { Chart } from 'chart.js'

@Component({
  selector: 'app-chart',
  template: `<canvas #myChart></canvas>,
})
export class SkillsChartComponent implements AfterViewInit {
  @ViewChild('myChart', {
    static: true
  }) myChart: {
    nativeElement: HTMLCanvasElement
  }

  constructor() {}

  ngAfterViewInit() {
    this.createChart()
  }

  private createChart(): void {
    const elem = this.myChart.nativeElement
    try {
      new Chart(elem, {
        type: 'pie',
        data: {
          labels: ["New", "In Progress", "On Hold"],
          datasets: [{
              label: '# of Votes',
              data: [1,2,3],
              backgroundColor: [
                'rgba(255, 99, 132, 1)',
                'rgba(54, 162, 235, 1)',
                'rgba(255, 206, 86, 1)'
              ],
              borderWidth: 1
            }]
          },
          options: {
          responsive: false,
          display:true
        }
      })
    } catch (err) {
      throw new Error(err) // You can either throw an error or do whatever here...
    } finally {
      // Any cleanup of logging...
    }
  }

}

What's going on here is that you're getting a reference to the Canvas itself, and using Angular's lifecycle hooks to say once the HTML content is initialized, then draw onto the canvas using Chart. The fact that the first argument of Chart is the element itself, this leads to a lot of anti-patterns, but you definitely don't need vanilla JS querying the document scope to get a handle on a specific canvas, and this is the best practice way of doing it according to Angular style guides. If you have concerns, leave them in the comments.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.