angulartypescriptchart.jsangular-ng-ifng2-charts

Toggle button doesn't show chart from db.json data


I'm starting to learn how to build Angular apps and I need to implement a toggle button that show/hide 2 charts, one chart has static data and the other on call data from a db.json file through a service an interface previously configurated.

I achieve to show both Charts.js without the toggle button, but when I add the event handling and the *ngIf directive to the section tag, it just show the first chart with the static data and a empty container with no chart.

These are my component.ts and .html files:

api-plot.component.ts:

import { AfterViewInit, Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ApiPlotService } from '../api-plot.service';
import { BaseChartDirective } from 'ng2-charts';
import { ChartOptions, ChartConfiguration, Chart, registerables } from 'chart.js';
import { ApiPlot } from '../api-plot';
Chart.register(...registerables)

@Component({
  standalone: true,
  selector: 'app-api-plot',
  imports: [BaseChartDirective, CommonModule],
  templateUrl: './api-plot.component.html',
  styleUrl: './api-plot.component.css'
})
export class ApiPlotComponent {
  
  showChart = false;

  toggleChart() {
    this.showChart = !this.showChart;
    // this.loadChartData;
    // this.renderChart;
  }

  chartData: ApiPlot[] = [];
  labelData: string[] = [];
  realData: number[] = [];
  stationData: string[] = [];

  public testChartData: ChartConfiguration<'line'>['data'] = {
    labels: [
      'January',
      'February',
      'March',
      'April',
      'May',
      'June',
      'July'
    ],
    datasets: [
      {
        data: [ 65, 59, 80, 81, 56, 55, 40 ],
        label: 'Series A',
        fill: true,
        tension: 0.5,
        borderColor: 'black',
        backgroundColor: 'rgba(255,0,0,0.3)'
      }
    ]
  };

  public testChartOptions: ChartOptions<'line'> = {
    responsive: true
  };
  public testChartLegend = true;
  
  constructor( private service: ApiPlotService ) {
  }
  
  loadChartData() {
    this.service.loadTimeSeriesData().subscribe( item => {
      this.chartData = item;
      if (this.chartData != null) {
        this.chartData.map( o => {
          this.labelData.push(o.timestamp);
          this.realData.push(o.value);
          this.stationData.push(o.station_code);
        });
  
        this.renderChart(this.labelData, this.realData, this.stationData);
      }
    });
  }
  
  renderChart( labelData: any, valueData: number[], stationData: any ) {
    const myChart = new Chart('lineChart', {
      type: 'line',
      data: {
        labels: labelData,
        datasets: [
          {
            label: 'Water Height in ml',
            data: valueData,
          }
        ]
      },
      options: {
        
      }
    });
  }
  
  // ngAfterViewInit(): void {
  //     this.loadChartData;
  // }

  // ngOnInit(): void {
  //   this.loadChartData();
  // }
}

api-plot.component.html:

<div class="api-call-container">
    <div>
        <h3 class="api-call-title">Graficar una collección de serie de tiempo:</h3>
    </div>
    <div class="api-buttons-container">
        <button class="plot-button" (click)="toggleChart()">{{ showChart ? 'Hide Chart' : 'Show Chart' }}</button>
        <button class="clean-button">Limpiar Gráfico</button>
    </div>
</div>
<section class="plot-results" *ngIf="showChart">
    <div class="div-test-results">
      <h2 class="test-plot-title">Line Chart from static data</h2>
      <canvas baseChart class="test-plot"
        [type]="'line'"
        [data]="testChartData"
        [options]="testChartOptions"
        [legend]="testChartLegend">
      </canvas>
    </div>
    <div class="div-line-results">
      <h2 class="plot-title">Line Chart from db.json data (uses inerface and service)</h2>
      <canvas baseChart id="lineChart"></canvas>
    </div>
</section>

api-plot.service.ts:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { ApiPlot } from './api-plot';
import { ApiPlotComponent } from './api-plot/api-plot.component';

@Injectable({
  providedIn: 'root'
})
export class ApiPlotService {

  constructor( private http: HttpClient ) {
    
  }

  // call to the api to plot the data within db.json
  loadTimeSeriesData() {
    return this.http.get<ApiPlot[]> ( "http://localhost:3000/water_heigth" )
  }
}

And this is the final result without the *ngIf in the section tag:

Final result with the w charts js

I tried to add some conditional to execute the function that load the data and render the Chart.js when the toggle variable change to true but without succces.

Also tried to apply ngAfterViewInit() to my component, but without succes.

If you need more info please let me know. Thanks in advance.


Solution

  • Because when loadChartData is executed immediately without waiting for the DOM:

    <canvas id="lineChart"></canvas>
    

    is rendered completely.

    Approach 1: Use setTimeout to delay the execution

    toggleChart() {
      this.showChart = !this.showChart;
    
      if (this.showChart)
        setTimeout(() => {
          this.loadChartData();
        });
    }
    

    Approach 2: Use AfterViewChecked lifecycle

    Execute the loadChartData function once the change detector completes checking for the component view's changes.

    ngAfterViewChecked() {
      this.showChart && this.loadChartData();
    }
    

    Demo @ StackBlitz