Create a new instance of the parent class in TypeScript to achieve class inheritance

Looking for a solution to extending a base class Collection in JavaScript/TypeScript to handle domain-specific use cases by implementing a "destructing" method like filter that returns a new instance with filtered elements. In PHP, you can achieve this using return new self(), but struggling to find an equivalent solution in JS/TS.

The current workaround involves overwriting the newInstance method in all child classes. Is there a more elegant solution to this issue?

class Collection<E> {
  protected items: E[];

  constructor(items: any[] = []) {
    this.items = items;
  }

  // struggling to create a new instance
  protected newInstance(items: E[]) {
    return new Collection(items);
  }

  size() {
    return this.items.length;
  }

  // filter and return new instance
  filter(callback: (item: any, index?: number) => boolean): this {
    return this.newInstance(this.items.filter(callback)) as this;
  }
}

class NumberCollection extends Collection<number> {
  sum() {
    return this.items.reduce((a, b) => a + b, 0);
  }
}

let numbers = new NumberCollection([1, 2, 3, 4]);

console.log(numbers.sum());

console.log(numbers.filter((n) => n > 1).sum());

Answer №1

If you want to achieve type safety, follow this method:

const { prototype } = Object.getPrototypeOf(this);
new prototype.constructor(...args);

For further details, check out Reference same class.

Answer №2

It seems like this answer may be incorrect (luckily), you can view an alternative answer here. I will keep the remainder of this response as is, unless I am able to remove it.


Regrettably, dealing with this situation is straightforward in JavaScript but quite cumbersome in TypeScript.

In JavaScript, you have two options:

  • new this.constructor(/*....*/) as previously mentioned.

  • The species pattern.

However, achieving the same in TypeScript requires type assertions or using @ts-ignore explicitly. Check out this related question regarding the species pattern, which concludes that it's not feasible in TypeScript.

If your filter method always returns an instance of the calling class (scenario #1 above, not #2), then the best approach might be to follow your implementation with a return type annotation using this and employing new this.constructor(/*...*/) with a @ts-ignore:

protected newInstance(items: E[]): this {
    // @ts-ignore - blech
    return new this.constructor(items);
}

Playground link

This method effectively generates an instance of the invoking class (even though TypeScript doesn't recognize the construction signature on this.constructor) because the prototype associated with a class instance typically contains a constructor property pointing back to the constructor function. In other words, this.constructor for an object created using new Collection would be Collection; while for an object created using new NumberCollection it would be NumberCollection. Thus, new this.constructor(/*...*/) creates a fresh object utilizing the constructor used to instantiate this. Consequently, the previous issue with sum no longer exists; the following code snippet functions correctly:

let numbers = new NumberCollection([1, 2, 3, 4]);
console.log(numbers.sum());
const x = numbers.filter((n) => n > 1);
//    ^? −− type is NumberCollection
console.log(x.sum()); // works

Although not ideal, we currently don't have better alternatives.

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

Adjust the transparency and add animation effects using React JS

When working on a React project, I encountered an issue where a square would appear under a paragraph when hovered over and disappear when no longer hovered. However, the transition was too abrupt for my liking, so I decided to implement a smoother change ...

Is the performance impacted by using try / catch instead of the `.catch` observable operator when handling XHR requests?

Recently, I encountered an interesting scenario. While evaluating a new project and reviewing the codebase, I noticed that all HTTP requests within the service files were enclosed in a JavaScript try / catch block instead of utilizing the .catch observable ...

Within an Angular test scenario, execute a static method from a service that triggers an HTTP get request to fetch stored JSON data. This data is then retrieved and returned back to the service

Currently, I am facing a challenge in my Angular test case where I am trying to load JSON data via an HTTP call. The issue arises when a static method is called from a service spec file named "url-service.spec" to another service named "load-json.service. ...

Zero-length in Nightmare.js screenshot buffer: an eerie sight

I'm currently working on a nightmare.js script that aims to capture screenshots of multiple elements on a given web page. The initial element is successfully captured, but any subsequent elements below the visible viewport are being captured with a l ...

AndroidStudio is obstinate when it comes to recognizing the hierarchy of inheritance

In my project, I am utilizing the library implementation 'org.json:json:20180813' I have developed a class called User which can be converted into a JSON string for data persistence purposes: data class User(override val name:String, override ...

Is it possible to pass multiple parameters in Angular by utilizing the click() function?

Is there a method for passing parameters with click() in Angular? <a asp-action="CreateSales" (click)="CreateSales(productname='pa', price='16.5')">Some Text</a> I am still learning Angular and would appreciat ...

Need for utilizing a decorator when implementing an interface

I am interested in implementing a rule that mandates certain members of a typescript interface to have decorators in their implementation. Below is an example of the interface I have: export interface InjectComponentDef<TComponent> { // TODO: How ...

I am unable to retrieve the values from a manually created JavaScript list using querySelectorAll()

const myList = document.createElement("div"); myList.setAttribute('id', 'name'); const list1 = document.createElement("ul"); const item1 = document.createElement("li"); let value1 = document.createTe ...

"An error occurs when attempting to access "this" in a static method as it

Just diving into the world of React and Typescript and currently exploring React hooks. Encountered a problem that's leaving me scratching my head. Here's the hook I'm using to fetch data: export const useFetch = <T>( fetchFunc: ( ...

Typescript may fall short in ensuring type safety for a basic reducer

I have been working on a simple reducer that uses an object to accumulate values, aiming to maximize TS inference. However, I am facing difficulties in achieving proper type safety with TypeScript. The issue arises when the empty object does not contain an ...

Require using .toString() method on an object during automatic conversion to a string

I'm interested in automating the process of utilizing an object's toString() method when it is implicitly converted to a string. Let's consider this example class: class Dog { name: string; constructor(name: string) { this.name = na ...

When imported, Node / JS instantly generates a new instance

Is there a way to instantiate a class without importing it first and using new afterward? Instead of var mainClass = require('../dist/main'); // has "class Main { ... }" var mainInstance = new mainClass(); I am looking for something like var ...

Applying ORM Drizzle in cases of conflict

Here's where I'm currently at: If I use onConflictDoNothing, the plan is to insert a new record into the database. However, if a record with the same userId and provider already exists, and the apiKey of the existing record is not equal to the ap ...

Is it possible to extract a single element from an array that is stored as a standard Observable?

Currently, I am using a regular observable instead of an observableArray. This observable keeps an array of elements which is defined as follows: public arrayOfItems: IArrayItem[]; public arrayOfItems$: BehaviorSubject<IArrayItem[]> = new BehaviorSu ...

The services generated by OpenAPI Generator typescript-angular are experiencing failures

While utilizing version 2.4.26 of this OpenApi generator ("@openapitools/openapi-generator-cli": "^2.4.26"), I am encountering issues with Angular services (Angular Version 13.2.0). For example, the services are passing too many arguments to the Angular Ht ...

Tips on continuously making calls to a backend API until receiving a successful response with status code 200

While working on my Angular project, I have encountered a situation where I need to make calls to a backend API. If the response is not 200 OK, I have to keep calling the API every 30 seconds until I receive a successful response. In Angular, I usually ca ...

Exploring the use of MockBackend to test a function that subsequently invokes the .map method

I've been working on writing unit tests for my service that deals with making Http requests. The service I have returns a Http.get() request followed by a .map() function. However, I'm facing issues with getting my mocked backend to respond in a ...

Creating a message factory in Typescript using generics

One scenario in my application requires me to define message structures using a simple TypeScript generic along with a basic message factory. Here is the solution I devised: export type Message< T extends string, P extends Record<string, any> ...

Create a custom definition for the useSelector function within a separate TypeScript file in a React

Question: Is it possible to define a type like <TRootState, string> in an external file and use it directly inside multiple Component files? External file: export type TUser = <TRootState, string> // This method does not work Component's ...

Updating an array of drag and drop elements in Angular Material

During my attempt to use drag and drop functionality with Angular Material, I encountered an issue with updating the `pos` key in a JSON array. Specifically, I wanted to set the `pos` value to the value of `event.currentIndex` while also adjusting the posi ...