angularrxjs

Why is chart not shown on page load?


Can someone help me with this one. I have a component where I want to show a chart from ng2-charts/chart.js. I wanted to make it a bit more reactive so therefore I wrote this:

My component:

import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { BaseChartDirective } from 'ng2-charts';
import { MyService } from './my.service';
import { AsyncPipe } from '@angular/common';

@Component({
  selector: 'app-component-one',
  standalone: true,
  imports: [BaseChartDirective, AsyncPipe],
  providers: [MyService],
  template: `
    @if(myDataVM$ | async; as myDataVM) {
    <div class="chart-wrapper">
      <canvas
        baseChart
        [type]="myDataVM.type"
        [options]="myDataVM.options"
        [data]="myDataVM.data"
        [plugins]="myDataVM.plugins"
      ></canvas>
    </div>
    }
  `,
  styles: [
    `
      .chart-wrapper {
        display: block;
        height: 85px;
      }
    `,
  ],
})
export class ComponentOneComponent implements OnChanges {
  @Input({ required: true }) data!: any;

  myDataVM$ = this.myService.myDataVM$;

  constructor(private myService: MyService) {}

  ngOnChanges(changes: SimpleChanges) {
    if (changes['data']) {
      const { currentValue } = changes['data'];
      this.updateChart(currentValue);
    }
  }

  private updateChart(data: any) {
    this.myService.updateChart(data);
  }
}

My Service:

import { Injectable } from '@angular/core';
import { ChartConfiguration, ChartData, ChartType, Plugin } from 'chart.js';
import { combineLatest, map, Observable, of, Subject } from 'rxjs';

@Injectable()
export class MyService {
  private myData$ = new Subject<any>();

  public chartType$: Observable<ChartType> = of('bar');

  public chartOptions$: Observable<ChartConfiguration['options']> = of({
    
    ...

  });

  public chartData$: Observable<ChartData<'bar'>> =
    this.myData$.pipe(
      map((data: any) => {
        
        ...

      })
    );

  public chartPlugins$: Observable<Plugin[]> = this.myData$.pipe(
    map((data: any) => {
      
      ...

    })
  );

  myDataVM$: Observable<any> = combineLatest([
    this.chartType$,
    this.chartOptions$,
    this.chartData$,
    this.chartPlugins$,
  ]).pipe(
    map(([type, options, data, plugins]) => ({
      type,
      options,
      data,
      plugins,
    }))
  );

  constructor() {}

  updateChart(data: any) {
    this.myData$.next(data);
  }
}

I simplified the component and the service for StackOverflow so ignore the any types. In general as you can see I have a service where I want to return a view model from the 4 observables that are in there. Two observables (type and options) have a fixed observable value, but two of them have to react to the changes in the myData$ subject.

This component works just fine if I add another trigger that calls updateChart method or if I use ngAfterViewInit, but I don't think I should use that in this case.

Also I don't know the initial state so therefore I didn't use BehaviorSubject if I did it loads just fine then.

Any suggestions?

Here is a stackblitz project:

https://stackblitz.com/edit/stackblitz-starters-naatq6?file=src%2Fmy.service.ts

Ignore that I hardcoded the data. I am mostly interested how can I get line 15 in myService to work instead of line 14.


Solution

  • The Problem

    Solutions

    private myData$ = new ReplaySubject<any>(1);
    
    myDataVM$ = toSignal(this.myService.myDataVM$);