Ionic3+Firestore: ng2-charts not loading

Hi, I’m trying to use ng2-charts in my Ionic 3 project to display a cumulative graph of data that I’m getting from Cloud Firestore, but the data received isn’t being plotted on the graph and I end up with a set of empty axes on the page. Here is my code:

cumulative.html:


<ion-content padding>
  <div>
    <div style="display: block">
      <canvas baseChart height="350"
              [datasets]="barChartData"
              [labels]="barChartLabels"
              [options]="barChartOptions"
              [legend]="barChartLegend"
              [chartType]="barChartType"
              (chartHover)="chartHovered($event)"
              (chartClick)="chartClicked($event)"></canvas>
    </div>
  </div> 
</ion-content>

cumulative.ts:

export class CumulativePage {

  public barChartOptions:any  = {
    scaleShowVerticalLines: false,
    responsive: true,
    scaleShowValues: true,
    scales: {
      yAxes: [{
        ticks: {
          beginAtZero: true
        }
      }],
      xAxes: [{
        ticks: {
          autoSkip: false
        }
      }]
    }
  }; 

  public barChartLabels:string[] = [];
  public barChartType:string = 'bar';
  public barChartLegend:boolean = true;

  public barChartData:any[] = [
    {data: [], label: 'time'}
  ];

  public chartClicked(e:any):void {
    console.log(e);
  }

  public chartHovered(e:any):void {
    console.log(e);
  }

  public classId: any;

  constructor(public navCtrl: NavController, public navParams: NavParams,
    public afs: AngularFirestore) {
  }

  ionViewDidLoad() {
    this.classId = this.navParams.get('className');
    console.log(this.classId);

    let queryCol = this.afs.collection('sessions', ref => ref.where('name', '==', this.classId));
    let query = queryCol.snapshotChanges();
    query.subscribe((snap) => {
      snap.forEach(q => {
        console.log(q.payload.doc.id);
        let sessionId = q.payload.doc.id;
        this.afs.collection('sessions').doc(sessionId).collection('helpList').ref.get()
          .then(snap => {
            snap.forEach(doc => {
              let data = doc.data() as Help;
              let exists = false;
              this.barChartLabels.forEach(user => {
                if (user == doc.data().userId){
                  let index = this.barChartLabels.indexOf(user);
                  this.barChartData[0].data[index] += doc.data().time;
                  exists = true;
                }
                else{
                  console.log(user, doc.data().userId);
                }
              })
              if (exists == false){
                this.barChartLabels.push(doc.data().userId);
                this.barChartData[0].data.push(doc.data().time);
                console.log(this.barChartLabels);
                console.log(this.barChartData);
              }    
            })

          })
      })
    })
    console.log(this.barChartLabels);
    console.log(this.barChartData);
  }
}

Apologies if my code’s not the best, I’m quite new to Ionic/firebase.

I’m trying to get two fields (user ID and time) from various documents in Firestore and add up the times to get a total time for each user (this.barChartLabels stores the user IDs and this.barChartData stores the total time).

When I console.log this.barChartLabels and this.barChartData, the correct data for both barChartLabels and barChartData appears in the console, but the data isn’t plotted on the graph.

My guess is that the data isn’t ready yet when the page loads, but I’m not sure. I’d really appreciate it if someone could help me figure out how to solve this!

That’s correct. How could it be? You aren’t even requesting the data until after the chart is rendered. You are playing a mind trick on yourself, putting your console.log after you have the data.

Create a provider. Put your async calls in the provider. Type the functions as Promises or Observables, depending. (Mostly Observables with Firestore). Render the data from the Observables in your page once the streams emit the data.

Hi, thanks so much for your reply, it makes a lot more sense. How do you type a function as an Observable (sorry if it’s a silly question)?

readFromDatabase(): Observable<ResponseType> {
  return databaseCall;
}

I’m still a little unsure about how to use Observables in this situation, so I tried using Promises and a provider but I’m still getting the same result (empty axes). Here’s my code:

cumulative.html:

<ion-content padding>
  <div>
    <div style="display: block">
      <canvas *ngIf="drawGraph == true" baseChart height="350" 
              [datasets]="barChartData"
              [labels]="barChartLabels"
              [options]="barChartOptions"
              [legend]="barChartLegend"
              [chartType]="barChartType"
              (chartHover)="chartHovered($event)"
              (chartClick)="chartClicked($event)"></canvas>
    </div>
  </div> 
</ion-content>

cumulative.ts:

export class CumulativePage {
 public barChartOptions:any  = {
    scaleShowVerticalLines: false,
    responsive: true,
    scaleShowValues: true,
    scales: {
      yAxes: [{
        ticks: {
          beginAtZero: true
        }
      }],
      xAxes: [{
        ticks: {
          autoSkip: false
        }
      }]
    }
  }; 

  public barChartLabels:string[] = [];
  public barChartType:string = 'bar';
  public barChartLegend:boolean = true;
 
  public barChartData:any[] = [
    {data: [], label: 'time'}
  ];
 
  public chartClicked(e:any):void {
    console.log(e);
  }

  public chartHovered(e:any):void {
    console.log(e);
  }

  public classId: any;
  public drawGraph = false;

  constructor(public navCtrl: NavController, public navParams: NavParams,
    public afs: AngularFirestore, public cProvider: CGraphProvider) {
  }

  fetchData(): Promise<any>{
    return this.cProvider.getData(this.classId, this.barChartLabels, this.barChartData)
      .then(event => {
        this.barChartLabels = event.barChartLabels;
        this.barChartData[0] = event[0];
      })
  }

  ionViewDidLoad() {
    this.classId = this.navParams.get('className');
    console.log(this.classId);

    this.fetchData().then(() => {
      this.drawGraph = true;
    })
  }
}

provider:

async getData(className, barChartLabels, barChartData): Promise<any>{
    let queryCol = this.afs.collection('sessions', ref => ref.where('name', '==', className));
    let query = queryCol.snapshotChanges();
    query.subscribe((snap) => {
      snap.forEach(q => {
        let sessionId = q.payload.doc.id;
        this.afs.collection('sessions').doc(sessionId).collection('helpList').ref.get()
          .then(snap => {
            snap.forEach(doc => {
              let data = doc.data() as Help;
              let id = doc.data().userId;
              let time = doc.data().time;
              let exists = false;
              barChartLabels.forEach(user => {
                if (user == doc.data().userId){
                  let index = barChartLabels.indexOf(user);
                  barChartData[0].data[index] += doc.data().time;
                  exists = true;
                }
              })
              if (exists == false){
                barChartLabels.push(doc.data().userId);
                barChartData[0].data.push(doc.data().time);
              }    
            })
          })
      })
    })
    return ({barChartLabels, ...barChartData});
  }