In my current project, I am working on developing reusable d3-based dashboard components within Angular 8. The goal is to create components such as a barchart that can be easily packaged into a module and reused without requiring any modifications to the component code, not even the constructor parameters. Additionally, I aim to have the ability for multiple sibling instances of these components to display different sets of data.
To achieve this, I have structured the component's API with input parameters for simple display configuration and a service interface that all components must adhere to for handling data and interactions. This service interface is implemented as an abstract base class, which the component's constructor accepts as a parameter (preventing users/developers from modifying constructor parameters or altering any component code).
However, one challenge I am facing is how to provide different implementations of the service base class to individual component instances without having to modify the constructor parameters. I previously attempted creating an abstract base class for the bar chart and then derived instances for specific bar chart services; however, this approach did not inherit the template and styles from the component base class.
export class BarChartComponent implements AfterViewInit
{
...
public constructor(public service: BarChartService) {}
public ngAfterViewInit() {
...
this.service.dataInit$.subscribe(() => {
let data = this.service.barChartData;
this.drawChart(data);
});
this.service.dataRefresh$.subscribe(() => {
let data = this.service.barChartData;
this.drawChart(data);
});
}
private drawChart(data): void {
...
}
}
@Injectable()
export abstract class BarChartService {
abstract barChartData: any;
abstract dataInit$: Observable<boolean>;
abstract dataRefresh$: Observable<boolean>;
abstract barChartSelect(string): void;
abstract barChartXValue: (d: any) => any;
abstract barChartYValue: (d: any) => any;
}
I am striving for multi-instance components that are reusable and capable of displaying various datasets. Any insights or suggestions on this matter would be highly appreciated.
To provide some clarity, here is an example of what a derived class of barChartService, like ApprovalsBarChartService, entails (where ApprovalService handles backend access logic and crossfilter shared across the dashboard):
@Injectable()
export class ApprovalsBarChartService implements BarChartService {
private init = new Subject<boolean>();
dataInit$ = this.init.asObservable();
private refresh = new Subject<boolean>();
dataRefresh$ = this.refresh.asObservable();
public get barChartData(): any {
return this.service.approvalsGroupedByApprover.all();
}
public barChartXValue = function (d) { return d.key; };
public barChartYValue = function (d) { return d.value; };
public constructor(public service: ApprovalService) {
this.service.init$.subscribe(() => {
this.init.next(true);
});
this.service.refresh$.subscribe(() => {
this.refresh.next(true);
});
}
public barChartSelect(processor: string): void {
this.service.approvalsIndexedByApprover.filter(processor);
this.service.selectedProcessor = processor;
this.service.selectItems = +this.service.xfilter.groupAll().reduceCount().value();
this.service.refreshCharts();
}
}