Search code examples
angulartypescriptchart.jsng2-charts

Chart.js bar chart with nested array


I would like to make a re-useable, stacked bar chart with @Input() properties in Angular. My data is a nested array.

stacked bar chart

I cannot figure out how to correctly setup my chart, I always get the error in the console:

Error: Uncaught (in promise): TypeError: Cannot read property '0' of undefined TypeError: Cannot read property '0' of undefined

The sample data:

barData = [[65, 59, 80, 81, 56, 55, 40], [28, 48, 40, 19, 86, 27, 90]];
barLabels = ["2006", "2007", "2008", "2009", "2010", "2011", "2012"];

The code:

  @Input() barData!: (number | ScatterDataPoint | BubbleDataPoint)[][];
  @Input() barLabels!: string[];

  public barChartType: ChartType = "bar";
  public barChartData: ChartConfiguration["data"];

  defaultBarChartData: Partial<ChartConfiguration["data"]> = {
    labels: this.barLabels,
    datasets: [
      {
        data: this.barData[0],
        label: "Income",
        backgroundColor: "#3ABD32"
      },
      {
        data: this.barData[1],
        label: "Expense",
        backgroundColor: "#E02A45"
      },
    ],
  };

  ngOnInit(): void {
    if (
      this.barData !== undefined &&
      this.barLabels !== undefined &&
      Array.isArray(this.barData) &&
      this.barData.length > 0 &&
      Array.isArray(this.barLabels) &&
      this.barLabels.length > 0
    ) {
      this.barChartData = {
        ...this.defaultBarChartData,
        ...{ labels: this.barLabels },
      } as ChartConfiguration["data"];
      this.barChartData.datasets[0].data = this.barData[0];
    } else {
      throw new Error("Charts must have their data and labels inputs defined.");
    }
  }
  public barChartOptions: ChartConfiguration["options"] = {...}

Its template:

 <canvas
    baseChart
    [data]="barChartData"
    [options]="barChartOptions"
    [type]="barChartType">
  </canvas>

Could someone help me find the error in my setup, please?


Solution

  • Move the initializing value to defaultBarChartData logic to ngOnInit method.

    From Angular - Input,

    Decorator that marks a class field as an input property and supplies configuration metadata. The input property is bound to a DOM property in the template. During change detection, Angular automatically updates the data property with the DOM property's value.

    To guarantee the variables with @Input() decorator are updated with value, thus the ngOnInit is needed.

    ngOnInit()

    Initialize the directive or component after Angular first displays the data-bound properties and sets the directive or component's input properties.

    defaultBarChartData: Partial<ChartConfiguration['data']>;
    
    ngOnInit(): void {
      this.defaultBarChartData = {
        labels: this.barLabels,
        datasets: [
          {
            data: this.barData[0],
            label: 'Income',
            backgroundColor: '#3ABD32',
          },
          {
            data: this.barData[1],
            label: 'Expense',
            backgroundColor: '#E02A45',
          },
        ],
      };
    
      ...
    }
    

    Sample StackBlitz Demo