Extending Two Classes in Typescript: A Complete Guide

I am looking for a way to efficiently save time by reusing common code across classes that extend PIXI classes within 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;
    }
}

The challenge I face is that the code inside getKeyInManager and setKeyInManager is static and I wish to avoid duplication. Here is the current 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;
    }
}

My goal is to automatically add, through Manager.add(), the key used in the manager as a reference within the object itself in its property _keyInManager.

For example, let's consider a Texture. Here is 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 using this.add(), I want the Game.Managers.Manager add() method to invoke a method on the object returned by

Game.Core.Texture.fromImage("/" + relativePath)
. This object, in this instance, would be a Texture:

module Game.Core {
    // Must extend PIXI.Texture, but need to inject methods from IManagedObject.
    export class Texture extends PIXI.Texture {

    }
}

Despite knowing that IManagedObject is an interface and cannot contain implementation, I am unsure how to incorporate the class ObjectThatShouldAlsoBeExtended into my Texture class. The same process would apply to Sprite, TilingSprite, Layer, and other classes.

I seek advice from experienced TypeScript developers on accomplishing this task. While multiple extends are not viable due to limitations, there must be a solution available that I have yet to discover.

Answer №1

Discover a lesser-known feature in TypeScript that unlocks the power of Mixins for creating reusable small objects. By combining Mixins, you can construct larger objects through multiple inheritance - a capability not available for classes but permissible for mixins, akin to interfaces with an associated implementation.

For further insights into TypeScript Mixins, check out this resource.

Consider leveraging this technique to share common components across various classes within your game and efficiently reuse these components from a single class:

Here's a demonstration of Mixins in action... starting with the desired flavors for mixing:

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

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

Next, the key method for Mixin creation (you only need to implement this once within 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];
            }
        });
    }); 
}

Now, you can craft classes with multiple inheritance using mixin flavors:

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

It's important to note that this class doesn't contain actual implementations - just enough to fulfill the "interfaces" requirements. However, when utilized, everything seamlessly integrates:

var being = new Being();

// Zzzzzzz.
being.sleep();

Answer №2

If you're looking to improve your coding skills, I recommend exploring the new mixins approach outlined here:

Unlike the "applyMixins" method suggested by Fenton, this new approach offers better assistance from the autocompiler, providing a clear overview of methods and properties inherited from both base and secondary classes.

To try it out yourself, check out the TS Playground site.

Here is how you can implement it in your code:

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

Answer №3

With TypeScript's support for decorators, combined with a handy library called typescript-mix, you can easily implement mixins to achieve multiple inheritance in just a few lines of code.

// The next line is added for better intellisense functionality
interface Shopperholic extends Buyer, Transportable {}

class Shopperholic {
  // By using the @use decorator, we are able to incorporate features from other classes
  @use( Buyer, Transportable ) this 
  price = 2000;
}

Answer №4

When considering an alternative method, it's important to prioritize solid type-safety and scalability.

The first step is to define interfaces that should be implemented in the target class:

interface IBar {
  doBarThings(): void;
}

interface IBazz {
  doBazzThings(): void;
}

class Foo implements IBar, IBazz {}

Next, implementation needs to be added to the Foo class. This can be achieved using class mixins that also adhere to 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!");
    }
  };
}

Enhance the Foo class with these 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

Answer №5

Unfortunately, TypeScript lacks support for multiple inheritance, making it challenging to find a straightforward solution. You may need to restructure your program to work around this limitation.

Consider the following suggestions:

  • If the additional class shares behavior with many subclasses, consider inserting it into the class hierarchy at a higher level. For example, deriving the common superclass of Sprite, Texture, Layer, etc., from this new class can be a good approach. It's essential to place it thoughtfully in the type hierarchy to maintain logical relationships between objects. Remember that inheritance signifies an "Is a - relationship."

  • If the additional class doesn't fit logically into the hierarchy, you can opt for aggregation by adding an instance variable of that class type to a shared superclass of Sprite, Texture, Layer, and others. This establishes a "Has a - relationship" and allows access through getter/setter methods in all subclasses.

  • An alternative is converting your class into an interface and extending it across all necessary classes. While this might introduce some code redundancy, it can be a viable option in certain scenarios.

Ultimately, the choice of approach depends on your preferences. Personally, I recommend converting the class to an interface.

As a tip, TypeScript offers properties as syntactic sugar for getters and setters, providing convenience and readability. You may find more information on this feature here:

Answer №6

An alternative approach would involve creating a new parent class and using the prototype chain to inherit methods from multiple child classes.

class ChildA {
    public static x = 5
}

class ChildB {
    public static y = 6
}

class Parent {}

Object.setPrototypeOf(Parent, ChildA)
Object.setPrototypeOf(Parent, ChildB)

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

By setting the prototypes of Parent to those of ChildA and ChildB, all properties can be accessed seamlessly without triggering any warnings about non-existent properties.

Answer №7

When it comes to design patterns, there is a guiding principle known as "favoring composition over inheritance." This means that instead of inheriting Class B from Class A directly, it's better to incorporate an instance of Class A within Class B as a property. By doing so, you can leverage the functionalities of Class A within Class B seamlessly. For some examples illustrating this concept, check out here and here.

Answer №8

For my scenario, I implemented a method called concatenative inheritance, which proved to be effective. If you're looking for a similar approach, consider the following:

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;
}

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

If you're interested, I suggest checking out Eric Elliott's insightful articles on JavaScript inheritance:

  1. The Heart & Soul of Prototypal OO: Concatenative Inheritance
  2. 3 Different Kinds of Prototypal Inheritance

Answer №9

There are numerous insightful responses already provided, however, I would like to illustrate with an example the ability to incorporate additional functionality into the extended class;

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

class Ability1 {
    doTask() {
        console.log('Performing task');
    }
}

class Ability2 {
    rest() {
        console.log('Resting');
    }
}

class EnhancedClass implements Ability1, Ability2 {
    doTask: () => void = () => { };
    rest: () => void = () => { };


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

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

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

    speak(y: string) {
        console.log(`Just stating ${y}...`);
    }
}
combineFeatures(EnhancedClass, [Ability1, Ability2]);


let enhancedObject = new EnhancedClass();

enhancedObject.doTask();
enhancedObject.speak("something");
console.log(enhancedObject.x);

Answer №10

Utilize Dynamic Inheritance or Class Factory Function.

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

interface PartA {
  a: string;
}

interface PartB {
  b: string;
}

interface CombinedAB extends PartA, PartB {}

class EmptyEntity {}

function CreatePartA<P>(p: CustomConstructor<P> = EmptyEntity as any) {
  class SubPartA extends (p as any) implements PartA {
    a = 'Default A value';
  }
  return SubPartA as unknown as CustomConstructor<PartA & P>;
}

function CreatePartB<P>(p: CustomConstructor<P> = EmptyEntity as any) {
  class SubPartB extends (p as any) implements PartB {
    b = 'Default B value';
  }
  return SubPartB as unknown as CustomConstructor<PartB & P>;
}


class EntityC extends CreatePartA<PartB>(CreatePartB()) implements CombinedAB {}

Answer №11

Here is an example that I have enhanced, making sure that no duplicate private attributes are present. I encourage others to further enhance it.

type MixinStructure<T = {}, T2 = any> = {
  new(...args: (T2 extends new (...arg2: any) => any ? ConstructorParameters<T2> : any)): T
}
type TransformUnionToIntersection<U> = (U extends any ? (key: U) => void : never) extends (key: infer Intersected) => void ? Intersected : never;

function integrateMixins<T extends MixinStructure[] = any>(...constructs: T): MixinStructure<TransformUnionToIntersection<{ [index in keyof T]: InstanceType<T[index]>; }[number]>, T[0]> {
  let constructorList: Function[] = [];
  class EnhancedInstance {
    constructor(...arguments) {
      constructorList.map((element) => {
        element.apply(this, arguments)
      })
    }
  }
  constructs.forEach(baseConstructor => {
    Object.getOwnPropertyNames(baseConstructor.prototype).forEach(name => {
      if (name !== 'constructor') {
        EnhancedInstance.prototype[name] = baseConstructor.prototype[name];
      } else {
        constructorList.push(baseConstructor.prototype[name])
      }
    });
  });

  return EnhancedInstance as any
}


class Dot {
  o: string
  private _experiment() {
    console.log("private experiment")
  }
  constructor(id: number, phrase: string) {
    console.log('Dot', arguments)
  }
  attempt() {
    console.log("attempt")
    this._experiment()
  }
}

class DotTwo {
  constructor(id: number, phrase: string) {
    console.log('DotTwo', arguments)
  }
  trialTwo() {
    console.log("trialTwo")
  }
}

class DotThree {
  examineThree() {
    console.log("examineThree")
  }
}

class DotThirtyFour {
  evaluateFour() {
    console.log("evaluateFour")
  }
}
const NewDot = integrateMixins(Dot, DotTwo, DotThree, DotThirtyFour);
let success = new NewDot(1, 'hi');
success.attempt()
success.trialTwo()
success.examineThree()

Answer №12

Discovering a solution:

export interface InterfaceA {
    performTask(): void
}

export interface InterfaceB {
   
}

export class ClassA implements InterfaceA {
    performTask() {   }
}

export class ClassB implements InterfaceB {
    doTask() {   }
}

export interface CombinedAB extends ClassA, ClassB {}
export class CombinedAB {}

You can then

export class ClassC extends CombinedAB {}

or even

const instanceC = new CombinedAB {}

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

JavaScript date selector allowing the selection of multiple dates

As a beginner in JavaScript, I am struggling to create a datepicker input field that allows multiple selections. Despite researching for solutions, none seem to fit my specific case. Can anyone offer guidance on how to achieve this? Your help is greatly ap ...

angular2 ngif does not effectively conceal HTML elements when set to false

In the HTML file, I have the following code: <p *ngIf="!checklistsready"> not ready </p> <p *ngIf="checklistsready"> Ready </p> And in my TypeScript file, it looks like this: checklistsready: boolean = false; constructor( ...

Can you explain the functionality of sinon's stub.yields method?

The explanation given in the documentation for sinon regarding stub.yields is as follows: By using stub.yields([arg1, arg2, ...]), you are essentially performing a function similar to callsArg. This will result in the stub executing the first callback it ...

Using html data attributes to encode JSON data with strings

Looking for a way to pass data to JavaScript, I decided to create a template tag as shown below: from django.utils.safestring import mark_safe from django import template import json register = template.Library() @register.simple_tag def mydata(): r ...

Why am I seeing numbers in the output when I log the data from the res.write(data) function in Node.js?

While working with Node.js on my Raspberry Pi, I encountered an issue where reading a local file 'test.html' resulted in hex output instead of the expected HTML format. Can someone explain why this might be happening? Additionally, I am aware tha ...

Activate the child for an update

Welcome! I am a newcomer to Angular and would greatly appreciate any assistance. The parent component of my picker has the ability to create various rules for each option. However, these rules are dynamic and can change frequently. I need to ensure that ...

Distinguishing between client side rendering and server side rendering is the backbone of web development

My goal is to give users the option to request webpages from my website either directly from the server or by clicking on links, which will be managed by Backbone's router. When a user requests a webpage directly from the server, they will receive a ...

Navigating through Switch cases and If Statements with multiple arguments can be complex for beginners in the world of JavaScript

Hi there, I have a specific case that I'm struggling to find a solution for. I am using Angular with a Firebase back-end to retrieve true or false values from a variable called ref. The variable contains 4 properties with either true or false values - ...

Create a unique component in ReactJS with the react-google-maps package to enhance your user interface

I would like to integrate a custom marker component into the map, but I have observed that using react-google-maps/api does not support rendering custom components. To illustrate this, here is an example of the code I used: const CustomMarker = ({ text }) ...

Utilizing NLP stemming on web pages using JavaScript and PHP for enhanced browsing experience

I've been exploring how to incorporate and utilize stemming results with JavaScript and PHP pages on web browsers. After running node index.js in the VS Code terminal, I was able to retrieve the output word using the natural library: var natural = re ...

gulp-webpack is unable to locate node packages

Currently working on developing a modern Angular application. I have opted to use gulp-webpack for quick development builds. To handle my TypeScript bundling and node modules dependencies, I am relying on webpack. However, it seems that gulp-webpack is no ...

jQuery: add to either element

What is the most efficient way to use .appendTo for either one element or another based on their existence in the DOM? For instance, I would like to achieve the following: Preferred Method: .appendTo($('#div1') || $('#div2')); Less ...

Transferring Visitor's Country Code Between Two Web Applications Using .NET Framework (ASP/MVC)

I'm currently working on developing an application that collects user information such as country, city, and IP address of the website they're visiting. This data is then sent to another web application of mine, which will be automatically update ...

If you want to use the decorators plugin, make sure to include the 'decoratorsBeforeExport' option in your

Currently, I am utilizing Next.js along with TypeScript and attempting to integrate TypeORM into my project, like demonstrated below: @Entity() export class UserModel extends BaseEntity { @PrimaryGeneratedColumn('uuid') id: number } Unfortun ...

$http promise chain executing in an incorrect sequence

As a beginner in angularjs, my objective is straightforward. I aim to execute an ajax request to fetch data and, upon completion, I want to initiate a second call to retrieve another set of data based on the information from the initial set. To achieve th ...

Working through JSON arrays in JavaScript

Here is a JSON example: var user = {"id": "1", "name": "Emily"} If I select "Emily," how can I retrieve the value "1"? I attempted the following code snippet: for (key in user) { if (user.hasOwnProperty(key)) { console.log(key + " = " + use ...

The Google Chrome console is failing to display the accurate line numbers for JavaScript errors

Currently, I find myself grappling with debugging in an angular project built with ionic framework. Utilizing ion-router-outlet, I attempt to troubleshoot using the Google Chrome console. Unfortunately, the console is displaying inaccurate line numbers mak ...

GULP showing an error message "ACCESS DENIED"

Exploring the fascinating realm of Gulp for the first time has been quite an adventure. I have managed to create a gulpfile that effectively handles tasks like reading, writing, and compiling sass/css/js/html files. However, when I try to access the site o ...

Is there a way to find a substring that ends precisely at the end of a string?

Looking to rename a file, the name is: Planet.Earth.01.From.Pole.to.Pole.2006.1080p.HDDVD.x264.anoXmous_.mp4 I want to remove everything starting from 2006 onwards. I considered using JavaScript string methods to find the index of the unnecessary part an ...

What could be the reason for v-model not functioning properly?

Embarking on my Vue.js coding journey, I am currently following a straightforward online tutorial. Utilizing the vue-cli, I kickstarted my application and crafted a basic component named Test.vue. Within this component lies a simplistic input control conne ...