Enabling a superior class to retrieve settings from a subordinate class

I have a BaseModel class where I've implemented common static methods (fetch, fetchAll, create, etc.). Different models extend this class to leverage these methods.

import { fetcher } from "../utils/fetch";

export type BaseModelConfigType = {
  singularName: string,
  pluralName: string
}

export default class BaseModel {
  constructor(cfg: BaseModelConfigType) {
    if (!cfg.singularName || !cfg.pluralName) {
      throw new Error('BaseModel requires a singularName and pluralName');
    }
  }

  static async fetch(pluralName: string, id: string): Promise<any> {
    return await fetcher.get(`${pluralName}/${id}`);
  }
}

export type SynapseType = {
  id?: string,
  text: string,
  created_at?: string,
  updated_at?: string
}

export class Synapse extends BaseModel {
  id?: string;
  text!: string;
  created_at?: string;
  updated_at?: string;

  static config = {
    singularName: 'synapse',
    pluralName: 'synapses'
  }

  constructor(data: SynapseType) {
    super(Synapse.config);

    Object.assign(this, data);
  }

  static async fetch(id: string): Promise<Synapse> {
    const data: SynapseType = await super.fetch(Synapse.config.pluralName, id);
    return new Synapse(data);
  }
}

export class OtherModel extends Basemodel {...}

It seems redundant to repeat the same method in each child model that extends the base model. Is there a way to avoid this duplication?

My goal is to use super(Model.config) in the child model constructor and ensure that the BaseModel can access each child model's config execution methods.

How can I accomplish this?

Answer №1

If I were to refine this method, the revised version might resemble something as follows:

// Unfortunately, static abstract methods are not supported in TS. 
// In an ideal scenario, we would prefer compile-time enforcement.
export default class BaseModel {
  static get singularName(): string {
    throw new Error('Must supply name in derived class!');
  }

  static get pluralName(): string {
    throw new Error('Must supply name in derived class!');
  }

  // Commonly used names for these static object construction methods include
  // "of" and "from".
  static async create(id: string): Promise<any> {
    throw new Error('Must override in derived class!');
  }
}

export type SynapseType = {
  id?: string,
  text: string,
  created_at?: string,
  updated_at?: string
}

export class Synapse extends BaseModel {
  id?: string;
  text!: string;
  created_at?: string;
  updated_at?: string;

  static singularName: 'synapse'
  static pluralName: 'synapses'

  constructor(data: SynapseType) {
    super();
    Object.assign(this, data);
  }

  static async create(id: string): Promise<Synapse> {
    const data = await fetcher.get(`${this.pluralName}/${id}`);
    return new Synapse(data);
  }
}

This version brings about a few improvements. Duplication is minimized, and the redundant base class constructor has been eliminated. If you attempt to construct an object without overriding the static create method or forget to set the names, you will quickly receive an error. It would be preferable if these checks could happen at compile time. Additionally, the return types of the async static creation methods are now more strongly typed.

To enforce calling constructors via static methods exclusively when incorporating real logic into those static methods, you can use the type system as follows:

class Foo extends BaseModel {
    static singularName: 'foo'
    static pluralName: 'foos'

    private static unique = (() => { enum Bar { _ = '' }; return Bar })()
    constructor(_lock: typeof Foo.unique, public readonly id: string) {
        super();
    }

    static async create(id: string): Promise<Foo> {
    return new Foo(Foo.unique, id);
  }
}

// Attempting to call new Foo will result in an error
const foo = new Foo(Foo.unique, 'abc123'); // error
const bar = Foo.create('abc123'); // no issues

Playground

Edit

You inquired about my preferred method in the comments. Here's how I would approach it:

interface BaseModel {
  new (...args: any[]): any
  a: string
}

const factory = <C extends BaseModel>(Clazz: C) => {
  return async (...args: ConstructorParameters<C>): Promise<InstanceType<C>> => {
    const resp = await fetch('some-url');
    const data = await resp.json();
    return new Clazz(...args);
  }
}

class Foo {}
class Bar { static a = 'a' }

const makeFoo = factory(Foo); // fails, missing 'a'
const makeBar = factory(Bar);

By using this approach, the static members function as compile-time checks: any mismatches with the BaseModel interface will be immediately flagged by your IDE. There's no need for workarounds like enforcing constructor locks using enums as seen earlier -- simply refrain from exporting concrete classes and only export the makeWhatever factory functions.

Playground

Similar to Ruby, JavaScript embodies multiple paradigms: if the object-oriented methodology feels cumbersome, integrating functional code can certainly enhance the overall experience.

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

How to Retrieve a Remote File in Angular using $http.get() with OAuth Authentication

I have a unique situation where my users possess private files that require downloads by authenticated users. The server I am using initially downloads a file from S3 utilizing its own set of S3 app_id and secret_token credentials. Once the file has been d ...

Angular's HttpClient is stating that the property '.shareReplay' is not recognized on the type 'Observable'

Excuse me for asking what may seem like a basic question. I'm currently following a tutorial at this link: I have created the Service as shown in the tutorial, but I am getting an error that says Property '.shareReplay' does not exist on ty ...

What methods can I use to organize and visualize this qualitative data in a chart?

I have a dataset that I want to represent on a line chart. The data appears as follows: [ { date: 1, depth: 1, river: 'trent', }, { date: 1, depth: 7, river: &apo ...

Perform a calculation using data from one schema and store the result in a different schema within MongoDB using Node.js

var ItemSchema = new Schema({ name: {type: String}, size : {type: String}, price : { type: Number} }); var SizeSchema = new Schema({ sizeName: {type: String}, dimensions : {type: String} }); F ...

I am looking to set an HTML file as the homepage for my Next.js project

Is there a way in next.js to render a normal .html file (index.html) when someone visits my root directory at "https://example.com/"? I have researched and edited my config file as shown below, /** @type {import('next').NextConfig} */ const next ...

The ajaxForm function is failing to execute properly and is not providing any error messages

When trying to use jQuery ajaxForm, I encounter a strange issue. I am attempting to set up a form for file upload with progress percentage tracking. However, my ajaxForm function does not seem to be triggering at all. Below is the code snippet I am usin ...

The power of RXJS's combineLatest operator

Upon using the combineLatest operator, I encountered an unexpected response when adjusting the interval duration of the first and second observables. Here is an example of the code: let intObs1$ = interval(1000).pipe(take(3)); let intObs2$ = interval( ...

Ways to effectively utilize asynchronous programming in your code?

Currently, I am using a for loop to compare against some values. The issue arises when I try to access console.log(indirizzo[a]);, as it returns undefined. However, if I use console.log(indirizzo);, all the values are displayed. var indirizzo = []; ...

Issue with radio button list not functioning properly in Mozilla and Chrome browsers

My web application has a radiobuttonlist with an onclick event. It functions properly in IE, but not in some other browsers. Here is a snippet of the code: <asp:RadioButtonList ID="rbgThreadStatus" runat="server" RepeatDirection=&quo ...

The JavaScript function for converting a date to a local string in the format of DD MMM YYYY is causing an error message in the browser console stating that it is not a valid function

I am encountering an issue with formatting a date string. The date is currently in the format 2021-03-31T00:00:00, and I need it to be displayed as 31 Mar 2021. In my TypeScript code, I attempted to use the following function: const formattedDate = i.Susp ...

The show more/show less link for a long jQuery paragraph is malfunctioning

I encountered an issue while coding where the "read more" link works correctly, but the "show less" link does not. Despite my efforts, I cannot seem to identify the error. Within this code snippet, there is an anchor tag with class="show-less" that I am u ...

Is it possible to accomplish this task using only Javascript?

Looking to create a JavaScript string without relying on C# calls Formatter = @$"function(value, opts) {{ if (value === undefined) {{return '';}} return Intl.NumberFormat('{CultureInfo.CurrentCulture.Name}', {{ style: 'currenc ...

What is the best way to streamline the creation of a "products filter" using Node.js and Angular?

I have decided to build an angular application for an online computer store, and I am using a node/express backend. One of the key features of the application is the products page, where users can view all the products available in our database. Each produ ...

Converting the Angular Material Select Demo Stackblitz into a Self-Contained Component?

Attempting to transform the Angular Material Select Demo into a self-contained component. Check out the Stackblitz here. Here are the necessary steps: Replace main.ts with the following (To create standalone component): import { bootstrapApplication } f ...

Unable to display divs in a stacked format using Bootstrap 5

I need help aligning div's vertically on the page. Currently, all my elements are displaying next to each other instead of stacking one below the other. body { height: 100vh; } <link href="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi ...

Error: Papa is not defined. The file was loaded from the CDN in the header section

I have integrated the cdn hosted lib for PapaParse in my HTML header. However, when I execute my JavaScript file and it reaches the function where I call Papa.unparse(data); It throws an error stating that Papa is undefined. This has left me puzzled as I h ...

HTML, JavaScript, and PHP elements combine to create interactive radio buttons that trigger the appearance and disappearance of mod

On my page, I have multiple foreach loops generating divs with different information. These divs are displayed in modals using Bootstrap. However, I am encountering an issue where if I select a radio button and then close the modal and open another one, th ...

HTML2Canvas now offers full compatibility with Scalable Vector Graphics (

Looking to snag a screenshot of a div that contains svg elements and other components using html2canvas. I came across 2 different versions of html2canvas. Version 1 Version 2 What sets them apart, and do either of them support svg elements and work wel ...

What is the reason for this assignment not being activated?

After going through the official xstate tutorial, I decided to create my own state machine inspired by a post on dev.to by a member of the xstate team. Everything is working fine except for the fact that the output is not being updated. It seems like the ...

Having trouble displaying the JQuery datepicker on my PHP webpage

I tried implementing a datepicker on my PHP page by following the source code on the jQuery website, but I'm having trouble getting the calendar to display. It seems like jQuery might not be loading correctly for this specific page. Can someone help m ...