Inquiry regarding classifications, Maps, and subcategories in typescript

In my project, I have a class called Chart that has various subclasses like BarChart and TimeseriesChart, all of which extend the base Chart class. To create these charts, I use a method named buildChart. This method uses an enum called ChartsEnum (examples include stackedTimeseries and barChart) to map the correct class using a Map:

export function buildChart(type: Charts, data: Array<TimeseriesData>) {
   
   var chartsMap = new Map<Charts, InstantiableAbstractClass<typeof Chart>([
        [Charts.stackedTimeseries, TimeseriesChart],
        [Charts.barChart, BarChart],
    ])
    const chart = chartsMap.get(type)
    return new chart(data).chart;

}

The definition for the type InstantiableAbstractClass is as follows:

export declare type InstantiableClass<T> = (new ( ...args: any) => { [x: string]: any }) & T;

For instance, the constructor of the TimeseriesChart class looks like this:

export class TimeseriesChart extends Chart{
    constructor(data: Array<TimeseriesData>) {
        super(data);
    }
}

Now, the goal is to add a new attribute called options to the chart class alongside the existing data attribute. The issue arises because different types of options are required for each ChartType (BarChart, TimeseriesChart). For example, BarChart needs these properties:

{
    start: number;
    end?: number;
}

while TimeseriesChart requires something like this:

{
    description: string;
}

If we update the constructor of TimeseriesChart with options, it would look like this:

export class TimeseriesChart extends Chart{
    constructor(data: Array<TimeseriesData>, options: TimeseriesChartOptions) {
        super(data, options);
    }
}

This necessitates adding a new argument options to the buildChart method, which can then be passed to specific classes (similar to how we pass the data argument).

What would be the best approach to handle this situation? One possibility could be utilizing generics and defining different types for options based on the subclasses, but adjusting the InstantiableAbstractClass type accordingly presents a challenge.

You can explore a complete example with additional explanations here.

Your guidance and insights are highly appreciated! Feel free to request more information if needed.

Thank you and best regards, Lukas

Answer №1

Initially, if you intend for your Chart class to be abstract and only have concrete subclasses, it's best to define it as such. To ensure the buildChart() method is overridden, I recommend using parameter properties for convenience in declaring fields and constructor parameters simultaneously. Adding an options parameter of type `any` for flexibility since strict typing isn't necessary at the base class level.

abstract class Chart {
    constructor(protected data: Array<TimeseriesData>, protected options: any) { }
    abstract buildChart(): void;
}

For sub-classes, narrow down the options property by utilizing the `declare` property modifier and specify the appropriate type in the constructor parameter:

interface BarChartOptions {
    start: number,
    end: number
}
class BarChart extends Chart {
    declare options: BarChartOptions;
    constructor(data: Array<TimeseriesData>, options: BarChartOptions) {
        super(data, options);
    }
    buildChart() { return {}; }
    protected getChartData() { }
}


interface TimeseriesChartOptions {
    description: string
}

class TimeseriesChart extends Chart {
    declare options: TimeseriesChartOptions;
    constructor(data: Array<TimeseriesData>, options: TimeseriesChartOptions) {
        super(data, options);
    }
    buildChart() { return { }; }
    protected getChartData() { }
}

To associate the `Charts` enum with different sub-classes, create a `chartConstructors` object mapping enums to constructors:

const chartConstructors = {
    [Charts.stackedTimeseries]: TimeseriesChart,
    [Charts.barChart]: BarChart
}

Add entries for each desired sub-class of `Chart`. Moving forward, let's develop the `buildChart()` function:

type ChartConstructorParameters<C extends Charts> = 
  ConstructorParameters<typeof chartConstructors[C]>;
type ChartInstance<C extends Charts> = 
  InstanceType<typeof chartConstructors[C]>;
type ChartConstructor<C extends Charts> = 
  new (...args: ChartConstructorParameters<C>) => ChartInstance<C>;

The `buildChart()` function is now implemented as a generic functional representation that constructs instances based on passed arguments dynamically:

function buildChart<C extends Charts>(
  type: C, ...ctorArgs: ChartConstructorParameters<C>
) {
    const chartConstructor = chartConstructors[type] as ChartConstructor<C>;
    return new chartConstructor(...ctorArgs);
}

This generic function accommodates various sub-classes' constructor parameters and outputs the corresponding instance. By invoking `buildChart`, ensuring compatibility between inputs and defined sub-classes within `chartConstructors` is verified.

Is the functionality operational?

const barChart = buildChart(Charts.barChart, [], { start: 1, end: 2 });
// const barChart: BarChart

const timeSeriesChart = buildChart(Charts.stackedTimeseries, [], { description: "" });
// const timeSeriesChart: TimeseriesChart

The results indicate success!

Access the code via TypeScript Playground.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Error throwing when attempting to use additional plugins in Angular CKEditor

I have been attempting to utilize the angular ckeditor extension found at: https://github.com/lemonde/angular-ckeditor UPDATE: I have already injected 'ckeditor' into the app modules. Within my partial html, I am including the following direc ...

Update the CSS for InputLabel

I have a drop-down list that I want to customize. The issue is illustrated below: https://i.sstatic.net/hzVtl.png I'm looking to center the text "choose format" within the field and adjust the font size. return ( <FormControl sx={{ m: 1 ...

obtain a Javascript variable within a Jade template by using inline code

script. var hide_section = 'block'; .title(style='display:#{hide_section}') I received an undefined value, any thoughts on why? Could it be because #{hide_section} is trying to access a variable from the back-end, such as the cont ...

What is the best way to include message body in CDATA using strophe?

I have a task to create messages in a specific format by using the following code: $msg({to: 'user', from: 'me', type: 'chat'}).c("body").t('some data'); This code generates the message structure as follows: <m ...

Navigating any object array in TypeScript and retrieving its properties

Consider the following JSON data object: var dataObjects = [ { "Name": "Date & Time", "Type": "Date", "Value": "2019-12-11" }, { "Name": "Activity", "Type": "String", ...

Incompatible parameter type for the Angular keyvalue pipe: the argument does not match the assigned parameter type

I need to display the keys and values of a map in my HTML file by iterating over it. To achieve this, I utilized Angular's *ngfor with the keyvalue pipe. However, I encountered an error when using ngFor: The argument type Map<string, BarcodeInfo ...

What is causing the chat-widget to display a null value for the style read property?

Could someone assist me with hiding the Widget-chat? I keep getting an error that the property of style is null. Any help would be greatly appreciated. Thank you in advance. document.getElementById("chat-widget").style.display='none'; ...

Providing Node-server with Vue.js Server Side Rendering

I've been attempting to deploy the Hackernews 2.0 demo on my Digital Ocean droplet but have hit a roadblock. npm run start starts the server on port :8080. npm run build is used for production builds. The specific build tasks can be found below: ...

I am currently facing an issue related to the length property. It is showing an ERROR TypeError: Cannot read property 'length' of undefined

Is it recommended to set the length to be inherited from Angular right? If so, why am I getting this error: "MyPostsComponent.html: 7 ERROR TypeError: Cannot read the 'length' of undefined property" when fileList.length is greater than 0? onFile ...

JavaScript and HTTP Post parameters: Consider using optional additional parameters

Managing a filtration function, I have an array of checkboxes and dropdowns. Users can select multiple checkboxes and dropdown values before clicking on the "Filter Now" button. Upon clicking the button, a POST request is triggered to my API, passing alon ...

Changing an array in PHP to a variable in JavaScript

I am struggling with converting a PHP array into a JavaScript variable using json_encode. When I print out my PHP variable: <?php $damage_array = $listing->tire_detail->damage_details; ?> It displays as: Array ( [lf] => 4 [rf] => 9 [lr ...

Transferring text file data into a database

I am currently dealing with sensor units in the field that are generating data in native text file format. This data needs to be imported into a database that has predefined tags such as mm, level, and voltage. The text files themselves are quite unstructu ...

What is the best way to inform the DOM about newly generated elements dynamically?

When making an AJAX call and generating HTML on the backend side, the result may not display with the desired properties such as cursor styling. For example, using jQuery to render JSON data: $(data.message).each(function (index, value) { $('#sta ...

JavaScript issue: event.target.innerText not preserving line breaks

Within a conteneditable div, I am displaying pre-populated grey text with the data-suggestion attribute. Upon clicking the text: The text color changes to black The data-suggestion attribute is removed from the selected line An input event on the conten ...

A combination of MVC6, tsd, and typings has proven to be

Given that TSD has been officially deprecated, I am looking towards the future and seeking guidance on how to use typings in the MVC6 framework. Any advice or tips would be greatly appreciated. I attempted following a tutorial from the typings website, wh ...

What causes variations in versions displayed by Node.js?

What causes this error to appear when using node version v16.17.1? https://i.sstatic.net/yJdEx.png ERROR: npm is known not to run on Node.js v10.19.0 UP--- I had multiple versions of node installed, with the default being an older version. I was able t ...

How can I prevent the enter key from working with Twitter Typeahead?

Is there a way to prevent the enter key from being pressed on an element within Twitter Typeahead's dropdown feature while using Angular with Typescript? I attempted to utilize preventDefault() when event.keycode === 13 on the ng-keydown event for th ...

bridging information from tables with text fields in forms

I am working on an HTML/CSS page that utilizes a table layout filled with buttons to mimic a T9 keypad design. Within the page, I have a form containing two text fields. My aim is to populate these text fields with numbers based on the values from the tab ...

Issues with updating values in Angular form controls are not being resolved even with the use of [formControl].valueChanges

[formControl].valueChanges is not triggering .html <span>Test</span> <input type="number" [formControl]="testForm"> .ts testData: EventEmitter<any> = new EventEmitter<any>(); testForm: FromCo ...

Issue with the date picker UI in react-datetime not displaying correctly?

<Datetime dateFormat={false} className="form-control" onChange={this.handleChange.bind(this)}/> I am currently utilizing the react-datetime library for time selection handleChange(newtime){ this.setState({MTtime: moment(newtime).format ...