Home > OS >  Typescript mixin fails when trying to use base class method
Typescript mixin fails when trying to use base class method

Time:11-18

MCVE

I would like to create a mixin, that can use a mehod of the class into which it was mixed. However this is not working.

////////////////////
// Simple class
class User {  
  sayHello(msg:string){
      console.log(msg)
  }
}
// Needed for all mixins
type Constructor<T = {}> = new (...args: any[]) => T;
////////////////////
// A mixin that adds a method that uses a base class method
function Activatable<TBase extends Constructor>(Base: TBase) {
  return class extends Base {    
    activate() {      
      (Base as unknown as User).sayHello("activated")
    }
  };
}
////////////////////
// Using the composed class
////////////////////
const ActivableUser = Activatable(User)
const activableUser = new ActivableUser()
activableUser.activate()

Run fails with [ERR]: Base.sayHello is not a function .

Is there any way I can create a mixin that has access to base class methods and properties?

CodePudding user response:

Base in the activate method is the class constructor. It is not an instance. And activate is an method on instances.

But since activate is an instance method, you can just use this instead.

Just replace Base with this and it works just fine.

function Activatable<TBase extends Constructor>(Base: TBase) {
  return class extends Base {    
    activate() {      
      (this as unknown as User).sayHello("activated")
    }
  };
}

Playground


Additionally, you can get rid of that nasty cast through unknown if you tell the mixin what instance methods it depends on.

function Activatable<
  TBase extends Constructor<{ sayHello(msg: string): void }>
>(Base: TBase) {
  return class extends Base {    
    activate() {      
      this.sayHello("activated")
    }
  };
}

Playground

CodePudding user response:

Fot completeness, based on @Alex Wayne's answer and his additional guidence in the comment, here is the solution I was really aiming for, namely to constrain the base class with an interface that it has to implement and require this interface from the base class in the mixin function.

Playground

////////////////////
// Interface that our base class has to implement
interface CanSayHello {
  sayHello(msg:string):void
}

////////////////////
// Base class
class User implements CanSayHello {  
  sayHello(msg:string){
      console.log(msg)
  }
}

////////////////////
// Needed for all mixins
type Constructor<T = {}> = new (...args: any[]) => T;

////////////////////
// A mixin that adds a method that uses a base class method
function Activatable<TBase extends Constructor<CanSayHello>>(Base: TBase) {
  return class extends Base {    
    activate() {      
      this.sayHello("activated")
    }
  };
}

////////////////////
// Creating the composed class
const ActivableUser = Activatable(User)
const activableUser = new ActivableUser()

////////////////////
// Using the composed class
activableUser.activate()
  • Related