Return the subclass from the constructor function

class X{
    constructor(input: string) {
        // do things
    }
    f() {console.log("X")}
}    
class Y extends X{
    constructor(input: string) {
        // do things
    }
    f() {console.log("Y")}
}
class Z extends X{
    constructor(input: string) {
        // do things
    }
    f() {console.log("Z")}
}

The current arrangement looks like this. It may seem unusual, but I am interested in having new X(inp) return an instance of either Y or Z based on the input (even if it's technically returned as X in TypeScript). This way, calling (new X("Y")).f() would output "Y", and the same for passing "Z", with "X" being the default behavior. Is there a way to achieve this without causing major violations? Right now, I have implemented a static method in X to handle this functionality, although using the constructor might be a cleaner option.

Answer №1

When a class constructor method explicitly uses a return statement to return an object that is not this, the object returned will be received when the class constructor is called with the new operator, instead of the new object created by the constructor.

class Foo {
  fooProp: string;
  constructor() {
    this.fooProp = "abc";
    return {
      fooProp: "zyx",
    }
  };
}
const f = new Foo();
console.log(f.fooProp) // Output: zyx

This scenario is not common and can lead to unexpected outcomes, such as instances not being recognized as part of their own constructor:

console.log(new Foo() instanceof Foo) // Output: false

It is advised to avoid utilizing this pattern unless necessary for specific reasons.

In TypeScript, it is possible to use return in a constructor method if the returned object belongs to a type compatible with the class type. This approach is acceptable when returning subclasses because they are directly related to the parent class.


One way to implement this behavior is demonstrated below:

class A {
  constructor(input?: string) {
    if (input === "B") return new B();
    if (input === "C") return new C();
  }
  f() { console.log("A") }
}
class B extends A {
  constructor() {
    super()
  }
  f() { console.log("B") }
}
class C extends A {
  f() { console.log("C") }
}

const a = new A().f(); // Output: A
const b = new A("B").f(); // Output: B
const c = new A("C").f(); // Output: C

By making A's constructor parameter optional and creating B and C without constructor parameters, you can selectively return subclass instances based on input strings while maintaining hierarchy within the classes.

// Testing the implementation:
const a = new A();
a.f(); // Output: A
const b = new A("B");
b.f(); // Output: B
const c = new A("C");
c.f(); // Output: C

console.log(a instanceof A); // true
console.log(b instanceof A); // true
console.log(c instanceof A); // true

This setup ensures expected results with clear distinctions between direct instances of A and its subclasses.


Although effective, using A's constructor for dual purposes can lead to confusion and errors. Consider making A's constructor a protected method and employing a static method for selecting the appropriate constructor to call, as illustrated below:

class A {
  protected constructor() { }
  f() { console.log("A") }
  public static make(input?: string) {
    if (input === "B") return new B();
    if (input === "C") return new C();
    return new A();
  }
}
class B extends A {
  f() { console.log("B") }
}
class C extends A {
  f() { console.log("C") }
}

const a = A.make();
a.f(); // Output: A
const b = A.make("B");
b.f(); // Output: B
const c = A.make("C");
c.f(); // Output: C

console.log(a instanceof A); // true
console.log(b instanceof A); // true
console.log(c instanceof A); // true

Despite slight differences in usage syntax, this method provides a clearer understanding of class instantiation choices for both internal implementations and external calls.

Explore the code in 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

Is it possible to compile a TypeScript file with webpack independently of a Vue application?

In my Vue 3 + Typescript app, using `npm run build` compiles the app into the `dist` folder for deployment. I have a web worker typescript file that I want to compile separately so it ends up in the root of the `dist` folder as `worker.js`. Here's wha ...

The `Required<Partial<Inner>>` does not inherit from `Inner`

I stumbled upon a code snippet that looks like this: type Inner = { a: string } type Foo<I extends Inner> = { f: I } interface Bar<I extends Inner> { b: I } type O<I extends Partial<Inner>> = Foo<Required<I>> & B ...

Guide to aligning a fraction in the center of a percentage on a Materal Design progress bar

Greetings! My objective is to create a material progress bar with the fraction displayed at the top of the percentage. https://i.sstatic.net/GbphJ.png Currently, I have managed to show the fraction at the beginning of the percentage. Below is the code sn ...

Inquiry regarding the return value of 'async-lock' in nodejs

I am utilizing the async-lock module in my typescript project to handle concurrency. However, I am encountering difficulties with returning the result within lock.acquire(...) {...}. Any guidance on how to resolve this issue would be greatly appreciated. ...

What is the best way to retrieve the post JSON data in the event of a 404 error?

When my service call returns a 404 error, I want to display the server's message indicating the status. The response includes a status code and message in JSON format for success or failure. This is an example of my current service call: this._trans ...

How can we pass an optional boolean prop in Vue 3?

Currently, I am in the process of developing an application using Vue 3 and TypeScript 4.4, bundled with Vite 2. Within my project, there exists a file named LoginPage.vue containing the following code: <script lang="ts" setup> const props ...

Verify if a given string exists within a defined ENUM in typescript

I have an enum called "Languages" with different language codes such as nl, fr, en, and de. export enum Languages { nl = 1, fr = 2, en = 3, de = 4 } Additionally, I have a constant variable named "language" assigned the value 'de'. My g ...

Exclude<Typography, 'color'> is not functioning correctly

Take a look at this sample code snippet: import { Typography, TypographyProps } from '@material-ui/core'; import { palette, PaletteProps } from '@material-ui/system'; import styled from '@emotion/styled'; type TextProps = Omi ...

What could be causing my sinon test to time out instead of throwing an error?

Here is the code snippet being tested: function timeout(): Promise<NodeJS.Timeout> { return new Promise(resolve => setTimeout(resolve, 0)); } async function router(publish: Publish): Promise<void> => { await timeout(); publish(&ap ...

Checkbox: Customize the appearance of the root element

I am trying to modify the root styles of a Checkbox component, but it is not working as expected. Here is my code: <CheckboxItem onChange={()} checked={isChecked} label="Show Checkb ...

When working with Expo and React Native in TypeScript, VS code IntelliSense recommends using 'react-native/types' instead of just 'react-native'

My React Native TypeScript setup is showing react-native/types instead of react-native in IntelliSense. How can I fix this issue? I initialized my project using npx create-expo-app MyApp --template Typescript. Here is my tsconfig.json configuration. { ...

"Linking a Next.js application with Azure's Application Insights for advanced insights

Trying to include my Next.js (TypeScript) app logs in Azure Application Insights has been a bit challenging. The documentation provided poor guidance, so I decided to follow this solution: https://medium.com/@nirbhayluthra/integrating-azure-application-ins ...

Is there intellisense support for Angular 2 templates in Visual Studio?

Is it possible for Visual Studio to provide autocomplete suggestions for properties of a Typescript component within a separate HTML view template? ...

Creating Concurrent Svelte Applications with Local State Management

Note: Self-answer provided. There are three primary methods in Svelte for passing data between components: 1. Utilizing Props This involves passing data from a parent component to a child component. Data transfer is one-way only. Data can only be passed ...

Enhance Material UI with custom properties

Is it possible to add custom props to a Material UI component? I am looking to include additional props beyond what is provided by the API for a specific component. For example, when using Link: https://material-ui.com/api/link/ According to the document ...

Assigning string properties to different types

I have numerous data types, each with a common property called dataType, and I am currently mapping each one to that specific value: interface GroupData { dataType: "group"; name: string; people: PersonData[]; } interface PersonData ...

What could be the reason for TypeScript allowing the injection of an invalid type?

I have the following objects and classes that demonstrate dependency injection: abstract class Animal { speak(): void {}; } class Dog implements Animal { speak(): void { console.log('Woof woof'); } } class Cat implements Ani ...

Is it possible for me to add a string to a URL as long as the string is not null?

When retrieving data from a database, I have the option to include specific parts for a more targeted search. Let's say I have an object structured like this: { title: "wonderland", aliases: "", ... } My goal now is to generate a URL for the ...

"Retrieving an element from an array may result in a value of

While going through an array of objects in my Angular component class, I faced a strange issue where the properties kept showing up as undefined. The function responsible for this behavior looks like this: upload(): void { const { fileHandles, related ...

Ensure all fields in an interface are nullable when using TypeScript

Is it possible to create type constraints in TypeScript that ensure all fields in an interface have a type of null? For example, if I want to write a validation function that replaces all false values with null, how can I achieve this? interface y { ...