franleplant
franleplant

Reputation: 639

Implement Pure Class mixins with Typescript

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.

Additional information:

Upvotes: 6

Views: 4054

Answers (3)

Vojta
Vojta

Reputation: 1623

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:

Upvotes: 8

Bruno Grieder
Bruno Grieder

Reputation: 29824

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()

Upvotes: 1

Nitzan Tomer
Nitzan Tomer

Reputation: 164167

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

Upvotes: 7

Related Questions