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.
The Problem
Solutions
private myData$ = new ReplaySubject<any>(1);
myDataVM$ = toSignal(this.myService.myDataVM$);