Search code examples
angularng2-charts

How to create one common angular ng2-charts (Bar chart) component which can be call from different components with different parameter


I am trying to create a common ng2-charts (Bar Stacked) component, where I can pass the data from other component and it will get update. Actually, I want to display multiple same stacked bar charts with different values in the same page while loading the page.

I have created one common component with ng-2 charts and created one service also. Now I am calling the common component from one more different component via shared service with different parameter, but it is always showing first component value.

my first component (bar-chart.component.ts),

import { Component, OnInit } from "@angular/core";
import { ChartOptions, ChartType, ChartDataSets } from "chart.js";
import * as pluginDataLabels from "chartjs-plugin-datalabels";
import { Label } from "ng2-charts";
import { BarChartService } from "../service/bar-chart.service";

@Component({
  selector: "app-bar-chart",
  templateUrl: "./bar-chart.component.html",
  styleUrls: ["./bar-chart.component.css"]
})
export class BarChartComponent implements OnInit {
  public barChartOptions: ChartOptions = {
    responsive: true,
    // We use these empty structures as placeholders for dynamic theming.
    scales: {
      xAxes: [
        {
          stacked: true
        }
      ],
      yAxes: [
        {
          stacked: true
        }
      ]
    },
    plugins: {
      datalabels: {
        anchor: "end",
        align: "end"
      }
    }
  };
  public barChartLabels: Label[] = [
    "2006",
    "2007",
    "2008",
    "2009",
    "2010",
    "2011",
    "2012"
  ];
  public barChartType: ChartType = "bar";
  public barChartLegend = true;
  public barChartPlugins = [pluginDataLabels]; 


  public barChartData: ChartDataSets[] = [];

  constructor(private barchartservice: BarChartService) {}

  ngOnInit() {
    this.barChartData = this.barchartservice.getBarChartData("one");
  }

  // events
  public chartClicked({
    event,
    active
  }: {
    event: MouseEvent;
    active: {}[];
  }): void {
    console.log(event, active);
  }

  public chartHovered({
    event,
    active
  }: {
    event: MouseEvent;
    active: {}[];
  }): void {
    console.log(event, active);
  }

  public randomize(): void {
    // Only Change 3 values
    const data = [
      Math.round(Math.random() * 100),
      59,
      80,
      Math.random() * 100,
      56,
      Math.random() * 100,
      40
    ];
    this.barChartData[0].data = data;
  }
}

in the first bar-chart.component.html,

 <div style="display: block">
          <canvas
            baseChart
            [datasets]="barChartData"
            [labels]="barChartLabels"
            [options]="barChartOptions"
            [plugins]="barChartPlugins"
            [legend]="barChartLegend"
            [chartType]="barChartType"
          >
          </canvas>
        </div>

In the second-bar-chart.component.ts,

import { Component, OnInit } from "@angular/core";
import { ChartOptions, ChartType, ChartDataSets } from "chart.js";
import * as pluginDataLabels from "chartjs-plugin-datalabels";
import { Label } from "ng2-charts";
import { BarChartService } from "../service/bar-chart.service";

@Component({
  selector: "app-second-bar-chart",
  templateUrl: "./second-bar-chart.component.html",
  styleUrls: ["./second-bar-chart.component.css"]
})
export class SecondBarChartComponent implements OnInit {
  public barChartData: ChartDataSets[] = [];
  constructor(private barchartservice: BarChartService) {}

  ngOnInit() {
    this.barChartData = this.barchartservice.getBarChartData("two");
  }
}

in the, second-bar-chart.component.html,

<h3>This is 2nd Bar Chart</h3>
<app-bar-chart></app-bar-chart>

in the service (bar-chart.service.ts), I am writing like this,

import { Injectable } from "@angular/core";

@Injectable({
  providedIn: "root"
})
export class BarChartService {
  constructor() {}

  getBarChartData(chartType) {
    if (chartType == "one") {
      return [
        { data: [65, 59, 80, 81, 56, 55, 40], label: "Series A", stack: "1" },
        { data: [28, 48, 40, 19, 86, 27, 90], label: "Series B", stack: "1" }
      ];
    } else {
      return [
        { data: [40, 59], label: "Male", stack: "1" },
        { data: [90, 48], label: "Female", stack: "1" }
      ];
    }
  }
}

I am expecting two different charts values but in both the component, it is showing only the first component chart value.

Please help me.


Solution

  • The problem with your code is the common component is not getting data from the parent component(2nd component) but it is getting its own data from the service using

    ngOnInit() {
        this.barChartData = this.barchartservice.getBarChartData("one");
      }
    

    One way would be to get barChartData outside of the component as Input property. In this way every parent component will give the necessary data to your common component and the job of your common component will be to just display it. You can use Input to get data from outside of component.

    You code will be like this:

    bar-chart.component.ts

    import { Component, OnInit, Input } from "@angular/core";
    import { ChartOptions, ChartType, ChartDataSets } from "chart.js";
    import * as pluginDataLabels from "chartjs-plugin-datalabels";
    import { Label } from "ng2-charts";
    import { BarChartService } from "../service/bar-chart.service";
    
    @Component({
      selector: "app-bar-chart",
      templateUrl: "./bar-chart.component.html",
      styleUrls: ["./bar-chart.component.css"]
    })
    export class BarChartComponent implements OnInit {
      @Input() barChartData: ChartDataSets[]; // <- note this line
      public barChartOptions: ChartOptions = {
        responsive: true,
        // We use these empty structures as placeholders for dynamic theming.
        scales: {
          xAxes: [
            {
              stacked: true
            }
          ],
          yAxes: [
            {
              stacked: true
            }
          ]
        },
        plugins: {
          datalabels: {
            anchor: "end",
            align: "end"
          }
        }
      };
      public barChartLabels: Label[] = [
        "2006",
        "2007",
        "2008",
        "2009",
        "2010",
        "2011",
        "2012"
      ];
      public barChartType: ChartType = "bar";
      public barChartLegend = true;
      public barChartPlugins = [pluginDataLabels]; 
    
    
    
      constructor(private barchartservice: BarChartService) {}
    
      ngOnInit() {}
    
      // events
      public chartClicked({
        event,
        active
      }: {
        event: MouseEvent;
        active: {}[];
      }): void {
        console.log(event, active);
      }
    
      public chartHovered({
        event,
        active
      }: {
        event: MouseEvent;
        active: {}[];
      }): void {
        console.log(event, active);
      }
    
      public randomize(): void {
        // Only Change 3 values
        const data = [
          Math.round(Math.random() * 100),
          59,
          80,
          Math.random() * 100,
          56,
          Math.random() * 100,
          40
        ];
        this.barChartData[0].data = data;
      }
    }
    

    Now in your parent component you can get the parentChartData through your Service and then give it to your common component like this:

    parent.component.html

    <app-first-bar-chart [barChartData]="parentChartData"></app-first-bar-chart>
    

    In this way, you can use this component anywhere in your app and just provide it with the data(barChartData) and it will display it

    Update

    In order to make your BarChartComponent fully reusable. You can get the other attributes(labels, chartType etc) of your chart from the parent component.

    You code will include other Input properties

    import { Component, OnInit, Input } from "@angular/core";
    import { ChartOptions, ChartType, ChartDataSets } from "chart.js";
    import * as pluginDataLabels from "chartjs-plugin-datalabels";
    import { Label } from "ng2-charts";
    import { BarChartService } from "../service/bar-chart.service";
    
    @Component({
      selector: "app-bar-chart",
      templateUrl: "./bar-chart.component.html",
      styleUrls: ["./bar-chart.component.css"]
    })
    export class BarChartComponent implements OnInit {
      @Input() barChartData: ChartDataSets[]; // <- note this line
      @Input() labels: Label[];
      @Input() chartType: ChartType;
    

    And your component tag would be like this:

    <app-first-bar-chart [chartType]="'bar'" [labels]="myLabelsArray" [barChartData]="parentChartData"></app-first-bar-chart>