The conclusion from utilizing the TypeScript class factory mixin is that it does not yield a constructor function

While attempting to utilize mixin classes in TypeScript, I encountered an issue. The return value of the mixin application (Sizeable.mixin() in the following code) is reported as "not a constructor function". However, it is puzzling because the error output clearly shows that the first part of its type is new (...args: any[]):, suggesting that it should indeed be a constructor function:

https://i.sstatic.net/ph3ES.png

Why could the Parent not be extended by a class even though it contains the { new (...args: any[]): portion in its type definition, indicating that it is a constructor?

Answer №1

The issue lies in the fact that a mixin will result in an intersection of what it adds with the original constructor type T. This functions correctly in a non-generic setting, creating a merged class. However, when used within another mixin, T is unknown, causing the intersection mxinStuff & T to be unresolvable:

function mixin1<T extends new (... a: any[]) => any> (ctor: T) {
    return class WithMixin1 extends ctor {
        mixin1() {}
    }
}

function mixinMixed<T extends new (... a: any[]) => any> (ctor: T) {
    return class WithMixin2 extends mixin1(ctor) { // Type '{ new (...a: any[]): mixin1<T>.WithMixin1; prototype: mixin1<any>.WithMixin1; } & T' is not a constructor function type.
        mixin2() {}
    }
}

To address this, we can manipulate the type returned by mixin1, making it usable as a base type for a new class and asserting its similarity to extending the original way:

function mixin1<T extends new (... a: any[]) => any> (ctor: T) {
    return class WithMixin1 extends ctor {
        mixin1() {}
        static staticMixin1() {}
    }
}
const mixin1BaseType = () => mixin1(class{});    
type Mixin1Type = ReturnType<typeof mixin1BaseType>

function mixinMixed<T extends new (... a: any[]) => {a : string }> (ctor: T) {
    class WithMixin2 extends (mixin1(ctor) as unknown as {
        new (... a: any[]): {a : string } 
    } & Mixin1Type ) {
        mixin2() {
            this.a
            this.mixin1()
            WithMixin2.staticMixin1();
        }
        static staticMixin2() {}
    }

    return WithMixin2 as unknown as {
        new (...a: ConstructorParameters<T>): WithMixin2 & InstanceType<T>
    } & T & Mixin1Type & typeof WithMixin2
}

type AnyContructor = new (... a: any[]) => any

let m = mixinMixed(class {
    a: string = "a";
    b: string = "b";
    m() {}
    static staticM() {}
})

let s  = new m();

// Access instance members
s.a
s.b
s.m();
s.mixin1();
s.mixin2();

// Access Static methods
m.staticMixin1()
m.staticMixin2()
m.staticM();
console.log(s)

Answer №2

The previous answer may seem complicated, but you can simplify it by using

InstanceType<typeof YourMixedType>
.

Check out this example from the TypeScript documentation

class Sprite {
  name = "";
  x = 0;
  y = 0;

  constructor(name: string) {
    this.name = name;
  }
}

type Constructor = new (...args: any[]) => {};

// Implementing a mixin that adds scaling functionality with private property encapsulation:

function Scale<TBase extends Constructor>(Base: TBase) {
  return class Scaling extends Base {
    // Using ES2020 private fields for encapsulation
    _scale = 1;

    setScale(scale: number) {
      this._scale = scale;
    }

    get scale(): number {
      return this._scale;
    }
  };
}

type Scalable = InstanceType<ReturnType<typeof Scale>>;

const EightBitSprite = Scale(Sprite);
type EightBitSpriteType = InstanceType<typeof EightBitSprite>;

const flappySprite = new EightBitSprite("Bird");

// Utilizing the defined types
foo(flappySprite);
bar(flappySprite);
baz(flappySprite);

function foo(sprite: EightBitSpriteType) {
}
function bar(sprite: Scalable) {
}
function baz(sprite: Sprite) {
}

Experience it live here.

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

Import reactjs modules without the need for Browserify, Webpack, or Babel

I am attempting to set up a TypeScript HTML application in Visual Studio. My goal is to incorporate reactjs v0.14.7 without relying on tools like Browserify. But, how can I utilize the react-dom module in this scenario? Let's set aside TypeScript fo ...

The object's value might be undefined, despite having null checks in place

Currently using Typescript 4.0.5 and encountering a persistent Object is possibly undefined error message My local state includes a .csv file uploaded by the user const [currentLine, setCurrentLine] = useState<File>(); The useEffect function monit ...

Angular Leaflet area selection feature

Having an issue with the leaflet-area-select plugin in my Angular9 project. Whenever I try to call this.map.selectArea, VSCode gives me an error saying that property 'selectArea' does not exist on type 'Map'. How can I resolve this? I& ...

Converting a Typescript project into a Node package: A step-by-step guide

I'm currently dealing with an older typescript project that has numerous functions and interfaces spread out across multiple files. Other packages that depend on these exports are directly linked to the file in the directory. My goal is to transition ...

Angular does not permit the use of the property proxyConfig

Click here to view the image I encountered an issue when attempting to include a proxy config file in angular.json, as it was stating that the property is not allowed. ...

Utilizing Typescript to create overload cascade constructors

I am facing a challenge in translating these Java constructor overloads to Typescript: public QueryMixin() { this(null, new DefaultQueryMetadata(), true); } public QueryMixin(QueryMetadata metadata) { this(null, metadata, true); } public QueryMi ...

Executing one controller function from another controller in Typescript

There are two typescript controllers in my project Controller A { public methodOfA() {//perform some action} } Controller B { public methodOfB() {//perform some action} } I am trying to implement the following functionality Controller B { ...

Import Information into Popup Window

When a user clicks on the "view" button, only the details of the corresponding challenge should be displayed: Currently, clicking on the "view" button loads all the challenges. This is because in my view-one-challenge.component.html, I have coded it as fo ...

Issue with the display of JQuery slider within TypeScript Angular directive in C# environment

I am encountering an issue with implementing a JQuery slider in my application. When I use it solely with JQuery, it functions properly. However, when I incorporate it into an angular directive built with typescript, the display is not as expected. https: ...

Changing a password on Firebase using Angular 5

I am in the process of developing a settings feature for user accounts on an application I've been working on. One key functionality I want to include is the ability for users to update their password directly from the account settings page. To enable ...

How to export a class from a function in Typescript

Here is the objective I'm aiming to accomplish: loadDependency(function(dep){ //error, can't use export within a function export class MyClass { myF(){ dep.myStuff(); } } }); I am restricted to creati ...

Using the $state feature in TypeScript with Svelte 5 for defining class fields

Currently, I have a class implementation as follows: class TestClass { prop = $state('test'); } This code works smoothly in files with the extension .svelte.js, and .svelte using <script lang="js">, but encounters issues in f ...

The utilization of functions from a implemented interface results in the generation of a 'non-function' error

I recently created an interface that includes variables and a function. However, I encountered an issue when trying to utilize the implemented function for a specific class as it resulted in an 'ERROR TypeError: ...getPrice is not a function" Below ...

Is there a way to transform time into a percentage with the help of the moment

I am looking to convert a specific time range into a percentage, but I'm unsure if moment.js is capable of handling this task. For example: let start = 08:00:00 // until let end = 09:00:00 In theory, this equates to 100%, however, my frontend data ...

Incorporating an external HTML template into an AngularJS 1.5 component with the powerful combination of Webpack and Types

I am attempting to incorporate an external html file into my Angular component: import { LoginController } from './login.controller'; import './login.scss'; import './login.html'; class LoginComponent implements ng.IComponen ...

TS2339: The object of type 'Y' does not contain a property named 'x'

Confusion arises as to why the TypeScript error is triggered in this piece of code. (Please disregard the irrelevant example given): interface Images { [key:string]: string; } function getMainImageUrl(images: Images): string { return images.main; } E ...

Best Practices for Integrating Angular with Your Custom JavaScript Library

Imagine needing to create a TypeScript function that can be utilized across various components, services, or modules. For example, let's say you want an alert wrapper like this: my_alert(msg); // function my_alert(msg) { alert(msg); } You might hav ...

How can you reposition a component within the dom using Angular?

Just started learning Angular, so I'm hoping this question is simple :) Without getting too specific with code, I could use some guidance to point me in the right direction. I'm currently developing a small shopping list application. The idea i ...

How can I show a limited number of columns in a mat-table in Angular 6 depending on a specific condition?

Currently, I am facing an issue with my mat table as it contains several columns. In a specific scenario, I need to hide two of these columns. Typically, for a regular table, we would use a simple ngIf condition to achieve this. However, in the case of thi ...

Validation of time input in Angular7 using reactive forms with Material design

Dealing with an angular 7 reactive form, the input field in this form allows users to view and modify time. The issue arises when users enter nonsensical numbers in this field that do not align with valid time ranges (e.g., hours should be between 1-24, mi ...