145

I want to save my time and reuse common code across classes that extend PIXI classes (a 2d webGl renderer library).

Object Interfaces:

module Game.Core {
    export interface IObject {}

    export interface IManagedObject extends IObject{
        getKeyInManager(key: string): string;
        setKeyInManager(key: string): IObject;
    }
}

My issue is that the code inside getKeyInManager and setKeyInManager will not change and I want to reuse it, not to duplicate it, here is the implementation:

export class ObjectThatShouldAlsoBeExtended{
    private _keyInManager: string;

    public getKeyInManager(key: string): string{
        return this._keyInManager;
    }

    public setKeyInManager(key: string): DisplayObject{
        this._keyInManager = key;
        return this;
    }
}

What I want to do is to automatically add, through a Manager.add(), the key used in the manager to reference the object inside the object itself in its property _keyInManager.

So, let's take an example with a Texture. Here goes the TextureManager

module Game.Managers {
    export class TextureManager extends Game.Managers.Manager {

        public createFromLocalImage(name: string, relativePath: string): Game.Core.Texture{
            return this.add(name, Game.Core.Texture.fromImage("/" + relativePath)).get(name);
        }
    }
}

When I do this.add(), I want the Game.Managers.Manager add() method to call a method which would exist on the object returned by Game.Core.Texture.fromImage("/" + relativePath). This object, in this case would be a Texture:

module Game.Core {
    // I must extend PIXI.Texture, but I need to inject the methods in IManagedObject.
    export class Texture extends PIXI.Texture {

    }
}

I know that IManagedObject is an interface and cannot contain implementation, but I don't know what to write to inject the class ObjectThatShouldAlsoBeExtended inside my Texture class. Knowing that the same process would be required for Sprite, TilingSprite, Layer and more.

I need experienced TypeScript feedback/advice here, it must be possible to do it, but not by multiple extends since only one is possible at the time, I didn't find any other solution.

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
Vadorequest
  • 16,593
  • 24
  • 118
  • 215

12 Answers12

132

There is a little known feature in TypeScript that allows you to use Mixins to create re-usable small objects. You can compose these into larger objects using multiple inheritance (multiple inheritance is not allowed for classes, but it is allowed for mixins - which are like interfaces with an associated implenentation).

More information on TypeScript Mixins

I think you could use this technique to share common components between many classes in your game and to re-use many of these components from a single class in your game:

Here is a quick Mixins demo... first, the flavours that you want to mix:

class CanEat {
    public eat() {
        alert('Munch Munch.');
    }
}

class CanSleep {
    sleep() {
        alert('Zzzzzzz.');
    }
}

Then the magic method for Mixin creation (you only need this once somewhere in your program...)

function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
             if (name !== 'constructor') {
                derivedCtor.prototype[name] = baseCtor.prototype[name];
            }
        });
    }); 
}

And then you can create classes with multiple inheritance from mixin flavours:

class Being implements CanEat, CanSleep {
        eat: () => void;
        sleep: () => void;
}
applyMixins (Being, [CanEat, CanSleep]);

Note that there is no actual implementation in this class - just enough to make it pass the requirements of the "interfaces". But when we use this class - it all works.

var being = new Being();

// Zzzzzzz...
being.sleep();
Fenton
  • 241,084
  • 71
  • 387
  • 401
  • 5
    Here's the Mixins section in the TypeScript Handbook (but Steve pretty much covered all you need to know in this answer, and in his linked article) http://www.typescriptlang.org/Handbook#mixins – Troy Gizzi Jul 22 '15 at 21:19
  • 3
    Typescript 2.2 now [supports Mixins](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html) – Flavien Volken Apr 27 '17 at 11:52
  • 1
    @FlavienVolken Do you know why Microsoft kept the old mixins section in their handbook documentation? By the way, the releases notes are realy difficult to understand for a beginner in TS like me. Any link for a tutorial with TS 2.2+ mixins? Thanks. – David Dahan Jul 03 '17 at 12:34
  • No idea, and honestly I would strongly recommend using a class cluster, a wrapper or facade pattern instead of mixins whenever you can, doing runtime mixins are not are not my cup of tea. It's not because we can that we should. – Flavien Volken Jul 03 '17 at 15:41
  • 4
    The "old way" to do mixins shown in this example is simpler than the "new way" (Typescript 2.2+). I don't know why they made it so difficult. – tocqueville Jul 14 '17 at 15:23
  • 2
    The reason is the "old way" can't get the type correctly. – unional Aug 04 '17 at 07:09
  • Great answer but would this be described as a "feature of Typescript" or rather something you can trick Typescript into accepting using some JS jiggerypokery? – El Ronnoco Jul 24 '20 at 08:01
  • @ElRonnocogood question - it is official (in that, it was in release notes). – Fenton Jul 24 '20 at 09:13
  • This is like traits in PHP – DeltaTango Jul 07 '22 at 00:18
  • Yeah but what about type-checking ...maybe a factory function that returns `Being & CanEat & CanSleep` – Alexander Mills Nov 09 '22 at 06:22
33

I would suggest using the new mixins approach described there: https://blogs.msdn.microsoft.com/typescript/2017/02/22/announcing-typescript-2-2/

This approach is better, than the "applyMixins" approach described by Fenton, because the autocompiler would help you and show all the methods / properties from the both base and 2nd inheritance classes.

This approach might be checked on the TS Playground site.

Here is the implementation:

class MainClass {
    testMainClass() {
        alert("testMainClass");
    }
}

const addSecondInheritance = (BaseClass: { new(...args) }) => {
    return class extends BaseClass {
        testSecondInheritance() {
            alert("testSecondInheritance");
        }
    }
}

// Prepare the new class, which "inherits" 2 classes (MainClass and the cass declared in the addSecondInheritance method)
const SecondInheritanceClass = addSecondInheritance(MainClass);
// Create object from the new prepared class
const secondInheritanceObj = new SecondInheritanceClass();
secondInheritanceObj.testMainClass();
secondInheritanceObj.testSecondInheritance();
Mark Dolbyrev
  • 1,887
  • 1
  • 17
  • 24
  • 1
    Is `SecondInheritanceClass` not defined on purpose or am I missing something? When loading this code in to the TS playground, it says `expecting =>`. Finally, could you break down exactly what is happening in the `addSecondInheritance` function, such as what's the purpose of `new (...args)`? – Seanny123 Aug 04 '17 at 03:59
  • The main point of such mixins implementation that ALL the methods and properties of the both classes will be shown in the autocomplete IDE help. If you want, you can define the 2nd class and use the approach suggested by Fenton, but in this case IDE autocomplete won't work. {new (...args)} - this code describes an object which should be a class (you could read about TS interfaces more in the handbook: http://www.typescriptlang.org/docs/handbook/interfaces.html – Mark Dolbyrev Aug 04 '17 at 19:01
  • 2
    the problem here is that TS still don't have a clue about modified class. I can type `secondInheritanceObj.some()` and don't get a warning message. – s-f Apr 11 '18 at 08:55
  • How to check if the Mixed class obey interfaces? – luthfianto Aug 20 '18 at 09:27
  • 34
    This 'new mixins approach' from Typescript looks like an afterthought. As a developer I just want to be able to say "I want this class to inherit ClassA and ClassB" or "I want this class to be a mixin of ClassA and ClassB" and i want to express that with clear syntax that I can remember in 6 months, not that mumbo jumbo. If it is a technical limitation of the possibilities of the TS compiler so be it, but this is not a solution. – Rui Marques Oct 12 '18 at 10:43
  • I've got an error when doing mixing with a generic type like `export class DashboardComponent extends Destroyable(MixinRoot)`, with the generic Destroyable class `export class Destroyable {}`. The typescript error is `value of type Destroyable is not callable. Did you mean to include 'new' ?` – Faly Jan 03 '19 at 09:45
  • @Mark Dolbyrev > how about a second class with attribute and methode that use this attribute? – Mahefa Mar 15 '21 at 17:52
32

I found an up-to-date & unparalleled solution: https://www.npmjs.com/package/ts-mixer

You are welcome :)

kenberkeley
  • 8,449
  • 3
  • 35
  • 25
  • 3
    I'm using nestjs and it has swagger working out of the box, that nestjs/swagger has its own decorators like `@ApiProperty` and others. And seems like these decorators don't get inherited and if I try wrapping them in `@decorator` I get a circular dependency error. Maybe someone encountered that error as well and came up with some solution? I would greatly appreciate it. – Albert Feb 15 '21 at 23:09
  • 2
    After trying to make multiple inheritance to work with the documented mixins pattern (apparently old version), with the non-documented mixins pattern (very noisy/boilerplaty), as well as make a custom decorator based library, testing multiple available libraries etc... I must say that ts-mixer is the easiest to work with and (at least for my use cases) it is the closest it comes to a solution without any hassles. This should be the accepted answer. – el.nicko Feb 28 '21 at 22:37
  • best answer !!! – flyingpluto7 Dec 11 '21 at 19:47
  • I am also looking for the the solution @Albert – Gambitier Jan 08 '22 at 07:56
  • 1
    Had a quick look at ts-mixer, seems to do the job indeed. Although, your answer should describe how it actually answers the OP's question, a simple link is not good-enough (doesn't respect SO rules). – Vadorequest Nov 02 '22 at 07:04
  • ```import { Mixin } from 'ts-mixer'; import DataProviderInterface from '../interfaces/DataProviderInterface'; import CommonProviderConstructor from '../interfaces/CommonProviderConstructor'; import ProfileDataProvider from './providers/ProfileDataProvider/ProfileDataProvider'; import DummyDataProvider from './providers/DummyDataProvider/DummyDataProvider'; class DataProvider extends Mixin( CommonProviderConstructor, ProfileDataProvider, DummyDataProvider, ) implements DataProviderInterface { } export default DataProvider; ``` nice =) – evtuhovdo Apr 04 '23 at 04:19
  • @Albert, you could check out NestJs mapped types if any of them could be of use – Layo Folaranmi May 26 '23 at 06:23
24

TypeScript supports decorators, and using that feature plus a little library called typescript-mix you can use mixins to have multiple inheritance with just a couple of lines

// The following line is only for intellisense to work
interface Shopperholic extends Buyer, Transportable {}

class Shopperholic {
  // The following line is where we "extend" from other 2 classes
  @use( Buyer, Transportable ) this 
  price = 2000;
}
fregante
  • 29,050
  • 14
  • 119
  • 159
Ivan Castellanos
  • 8,041
  • 1
  • 47
  • 42
14

I think there is a much better approach, that allows for solid type-safety and scalability.

First declare interfaces that you want to implement on your target class:

interface IBar {
  doBarThings(): void;
}

interface IBazz {
  doBazzThings(): void;
}

class Foo implements IBar, IBazz {}

Now we have to add the implementation to the Foo class. We can use class mixins that also implements these interfaces:

class Base {}

type Constructor<I = Base> = new (...args: any[]) => I;

function Bar<T extends Constructor>(constructor: T = Base as any) {
  return class extends constructor implements IBar {
    public doBarThings() {
      console.log("Do bar!");
    }
  };
}

function Bazz<T extends Constructor>(constructor: T = Base as any) {
  return class extends constructor implements IBazz {
    public doBazzThings() {
      console.log("Do bazz!");
    }
  };
}

Extend the Foo class with the class mixins:

class Foo extends Bar(Bazz()) implements IBar, IBazz {
  public doBarThings() {
    super.doBarThings();
    console.log("Override mixin");
  }
}

const foo = new Foo();
foo.doBazzThings(); // Do bazz!
foo.doBarThings(); // Do bar! // Override mixin
nomadoda
  • 4,561
  • 3
  • 30
  • 45
13

Unfortunately typescript does not support multiple inheritance. Therefore there is no completely trivial answer, you will probably have to restructure your program

Here are a few suggestions:

  • If this additional class contains behaviour that many of your subclasses share, it makes sense to insert it into the class hierarchy, somewhere at the top. Maybe you could derive the common superclass of Sprite, Texture, Layer, ... from this class ? This would be a good choice, if you can find a good spot in the type hirarchy. But I would not recommend to just insert this class at a random point. Inheritance expresses an "Is a - relationship" e.g. a dog is an animal, a texture is an instance of this class. You would have to ask yourself, if this really models the relationship between the objects in your code. A logical inheritance tree is very valuable

  • If the additional class does not fit logically into the type hierarchy, you could use aggregation. That means that you add an instance variable of the type of this class to a common superclass of Sprite, Texture, Layer, ... Then you can access the variable with its getter/setter in all subclasses. This models a "Has a - relationship".

  • You could also convert your class into an interface. Then you could extend the interface with all your classes but would have to implement the methods correctly in each class. This means some code redundancy but in this case not much.

You have to decide for yourself which approach you like best. Personally I would recommend to convert the class to an interface.

One tip: Typescript offers properties, which are syntactic sugar for getters and setters. You might want to take a look at this: http://blogs.microsoft.co.il/gilf/2013/01/22/creating-properties-in-typescript/

lhk
  • 27,458
  • 30
  • 122
  • 201
  • 3
    Interesting. **1)** I cannot do that, simply because I extends `PIXI` and I cannot change the library to add another class on top of it. **2)** It is one of the possible solution I could use, but I would have prefered to avoid it if possible. **3)** I definitively don't want to duplicate that code, it may be simple now, but what's gonna happen next? I've worked on this program only a day and I'll add a lot more later, not a good solution for me. I'll take a look at the tip, thanks for the detailled answer. – Vadorequest Nov 16 '14 at 01:36
  • Interesting link indeed, but I don't see any use case to solve the problem here. – Vadorequest Nov 16 '14 at 01:41
  • If all of these classes extend PIXI, then just make the ObjectThatShouldAlsoBeExtended class extend PIXI and derive the Texture, Sprite, ... Classes from that. That's what I meant with inserting the class in the type hirarchy – lhk Nov 16 '14 at 08:57
  • `PIXI` itself isn't a class, it's a module, it cannot be extended, but you're right, that would have been a viable option if it was! – Vadorequest Nov 16 '14 at 12:06
  • 1
    So each of the classes extends another class from the PIXI module ? Then you're right, you can't insert the ObjectThatShouldAlso class into the type hirarchy. That's not possible. You could still choose the has-a relationship or the interface. Since you want to describe a common behaviour that all of your classes share, I would recommend using an interface. That's the cleanest design, even if code is duplicated. – lhk Nov 16 '14 at 13:39
3

A very hacky solution would be to loop through the class you want to inherit from adding the functions one by one to the new parent class

class ChildA {
    public static x = 5
}

class ChildB {
    public static y = 6
}

class Parent {}

for (const property in ChildA) {
    Parent[property] = ChildA[property]
}
for (const property in ChildB) {
    Parent[property] = ChildB[property]
}


Parent.x
// 5
Parent.y
// 6

All properties of ChildA and ChildB can now be accessed from the Parent class, however they will not be recognised meaning that you will receive warnings such as Property 'x' does not exist on 'typeof Parent'

WillCooter
  • 31
  • 5
1

In design patterns there is a principle called "favouring composition over inheritance". It says instead of inheriting Class B from Class A ,put an instance of class A inside class B as a property and then you can use functionalities of class A inside class B. You can see some examples of that here and here.

AmirHossein Rezaei
  • 1,086
  • 1
  • 16
  • 20
1

In my case I used concatenative inheritance. Maybe for someone this way will be helpful:

class Sprite {
  x: number;
  y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}

class Plane extends Sprite {
  fly(): string {
    return 'I can Fly!'
  }
}

class Enemy {
  isEnemy = true;
}

class Player {
  isPlayer = true;
}

// You can create factory functions to create new instances
const enemyPlane = Object.assign(new Plane(1, 1), new Enemy());
const playerPlane = Object.assign(new Plane(2, 2), new Player());

Also I recommend reading Eric Elliott's articles about js inheritance:

  1. The Heart & Soul of Prototypal OO: Concatenative Inheritance
  2. 3 Different Kinds of Prototypal Inheritance
zemil
  • 3,235
  • 2
  • 24
  • 33
  • That's one way to go, never thought of that. Kind of a hack, but not so much. Maybe a link to the articles you mentioned would be helpful – Vadorequest Jun 29 '21 at 19:04
0

There are so many good answers here already, but i just want to show with an example that you can add additional functionality to the class being extended;

function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
            if (name !== 'constructor') {
                derivedCtor.prototype[name] = baseCtor.prototype[name];
            }
        });
    });
}

class Class1 {
    doWork() {
        console.log('Working');
    }
}

class Class2 {
    sleep() {
        console.log('Sleeping');
    }
}

class FatClass implements Class1, Class2 {
    doWork: () => void = () => { };
    sleep: () => void = () => { };


    x: number = 23;
    private _z: number = 80;

    get z(): number {
        return this._z;
    }

    set z(newZ) {
        this._z = newZ;
    }

    saySomething(y: string) {
        console.log(`Just saying ${y}...`);
    }
}
applyMixins(FatClass, [Class1, Class2]);


let fatClass = new FatClass();

fatClass.doWork();
fatClass.saySomething("nothing");
console.log(fatClass.x);
Orson
  • 14,981
  • 11
  • 56
  • 70
0

You can call Dynamic Inheritance or Class Factory.

type ClassConstructor<T> = {
  new (...args: any[]): T;
};

interface IA {
  a: string;
}

interface IB {
  b: string;
}

interface IAB extends IA, IB {}

class EmptyClass {}

function GetA<T>(t: ClassConstructor<T> = EmptyClass as any) {
  class A extends (t as any) implements IA {
    a = 'Default value a';
  }
  return A as unknown as ClassConstructor<IA & T>;
}

function GetB<T>(t: ClassConstructor<T> = EmptyClass as any) {
  class B extends (t as any) implements IB {
    b = 'Default value b';
  }
  return B as unknown as ClassConstructor<IB & T>;
}


class C extends GetA<IB>(GetB()) implements IAB {}
Ahmet Emrebas
  • 566
  • 6
  • 10
-1

Found a way:

export interface IsA {
    aWork(): void
}

export interface IsB {
   
}

export class A implements IsA {
    aWork() {   }
}

export class B implements IsB {
    bWork() {   }
}

export interface IsAB extends A, B {}
export class IsAB {}

Then you can

export class C extends IsAB {}

or even

const c = new IsAB {}
TheGeekZn
  • 3,696
  • 10
  • 55
  • 91