6

Im trying to define a pure class based mixin function, but I cannot get the type signature right for this one.

The intent is to provide a function that accepts any Class A as parameter and returns a new Class B that extends the original Class A.

export function mixin<A>(myclass: A) {
  return class B extends A {
    newMethod() {
      //stuff
    }
  }
}

As I said, I cannot figure this out.

  • I need a way to express that A needs to be a class.

  • I also need to express the return type, which yields several errors, among:

    error TS4060: Return type of exported function has or is using private name 'B'.

Additional information:

  • This is inside a utils.ts module that is exported so that other modules can use it
  • This is all being worked in the context of a library I am writing
3
  • Take a look at typeof A. Commented Dec 25, 2016 at 6:04
  • It is not a mixin, just inheritance, no? Commented Dec 25, 2016 at 7:36
  • Thanks to both, please note that A is anything, I don't know what thing can be passed as parameter, the only restriction is that I want it to be a Class. As for the just inheritance, it kind of is! But the difference is that you can add methods and attributes to A just like the old mixin would do Commented Dec 25, 2016 at 15:20

3 Answers 3

8

I wanted to use mixins in TypeScript 2.2 with

  • abstract base class
  • decorators on mixins properties (@Parameter)
  • export mixin definition (Feature) and mixin application (Derived) from module
  • generate declaration file ("declaration": true in tsconfig.json)

I started with following code which unfortunately does NOT work:

export type Constructor<T> = new(...args: any[]) => T;

export abstract class AbstractBase {
    fieldA = "a";
}

export function Feature<T extends Constructor<object>>(Base: T) {
    return class extends Base {
        @Parameter()
        fieldB = "b";
    };
}

export class Derived extends Feature(AbstractBase) {
    fieldC = "c";
}

Finally I ended up with following code which is more complicated but works as expected:

export type Constructor<T> = new(...args: any[]) => T;

export abstract class AbstractBase {
    fieldA = "a";
}

export interface Feature {
    fieldB: string;
}

export function Feature<T extends Constructor<object>>(Base: T): T & Constructor<Feature> {
    class TWithFeature extends Base implements Feature {
        @Parameter()
        fieldB = "b";
    }
    return TWithFeature;
}

export const AbstractBaseWithFeature = Feature(AbstractBase as Constructor<AbstractBase>);

export class Derived extends AbstractBaseWithFeature {
    fieldC = "c";
}

In addition to @bruno-grieder answer see following links:

Sign up to request clarification or add additional context in comments.

Comments

7

There's an open issue for this one: Allow class to extend from a generic type parameter

For now you can work around that with something like this:

interface Base{}
interface BaseClass<T> {
    new (): T
    readonly prototype: T;
}

function mixin<T extends Base>(baseClass: BaseClass<T>) {
    class B extends (baseClass as BaseClass<Base>) {
        newMethod() { }
    }

    return B as BaseClass<T & B>;
}

(code in playground)

Which is based on code from here: extends dynamic Base class with generic throw an error


Edit

You can define an interface for the methods which will be added by the new class, something like:

interface B {
    newMethod(): void;
}

function mixin<T extends Base>(baseClass: BaseClass<T>): BaseClass<T & B> {
    class BImpl extends (baseClass as BaseClass<Base>) implements B {
        newMethod() {
            console.log("B.newMethod");
        }
    }

    return BImpl as BaseClass<T & B>;
}

(code in playground)

Then you can export the B interface and then you can use it anywhere.
This code works well:

class A implements Base {
    method() {
        console.log("A.method");
    }
}

let NewA = mixin(A);
let newA = new NewA();
newA.method();
newA.newMethod();

Output:

A.method
B.newMethod

5 Comments

Thank you very much for your answer! It resolves 90% of my problems. The one thing remaining is that this mixin function is exported from inside a module and in turn imported by the index module, which makes typescript throw this errors: error TS4060: Return type of exported function has or is using private name 'B'.
Right. Your new class is only defined inside a function inside another module, there's no way for the other module to get the type information. I'm not sure what you're trying to achieve so it's hard to help, maybe edit your question and explain what it is that you're trying to do.
Thank you very much for your help! I've updated the original answer. The important part is that this is a utility function that is exported so that other modules (es6 modules not typescript modules) can use it as a generic class enhancer.
Check my revised answer
This works awesome! Thank you very much for your help! Happy end of the year!
1

With Typescript 2.2.1, I had (mostly export and visibility) issues with @Nitzan Tomer solution and had to resort to this, which I believe is closer to the mixin documentation and made my (Webstorm) IDE smarter

mixin.ts

export interface Constructor<T> {
    new( ...args: any[] ): T
}

export interface B {
    newMethod(): void;
}

export const mixin = <T extends Constructor<{}>>( baseClass: T ) =>
    class BImpl extends baseClass implements B {
        newMethod() {
            console.log( "B.newMethod" );
        }
    } as Constructor<B> & T

main.ts

import {B, Constructor, mixin} from './mixin'

class A {
    method() {
        console.log( "A.method" )
    }
}

const AB: (typeof A) & Constructor<B>  = mixin( A )
const ab = new AB()
ab.method()
ab.newMethod()

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.