0

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 configured.

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.

1
  • Hi, done, I added my app service with the function, which read the data stored in the db.json file. Commented Apr 28 at 13:32

2 Answers 2

1

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

Sign up to request clarification or add additional context in comments.

1 Comment

as note: setTimeout strictly not delay the execution. setTimeout force redraw after the execution of the function. (Sorry for the clarification, you know I'm quite fussy).
0

As Young say the problem is that when you call to renderChart, the <canvas> it's not in the DOM.

You can use

<section class="plot-results" [style.display]="showChart?null:'hidden'>
   ...
</section>

Or, when toogle call to render enclosed in a setTimeout

  toggleChart() {
    this.showChart = !this.showChart;

    if (this.showChart)
    {
      setTimeout(() => {
        //see you call to renderChart, not to loadChart
        //your data can be loaded in ngOnInit
        this.renderChart(this.labelData, this.realData, this.stationData);
      });
    }
  }

NOTE: NOT use ngAfterViewChecked. This is executed several, several times

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.