I'm trying to use Typescript Mixins to compose my class instead of duplicating behaviors (like the getImage function below).
I followed those instructions https://www.typescriptlang.org/docs/handbook/mixins.html and it worked well.
But there's a problem I can't solve is that now I can't use my classes with mixins as a type anymore (see the functions at the end).
Do you know how I could get around this?
Thanks
// A - Some Mixins
type Constructor<T = {}> = new (...args: any[]) => T;
function withStoredImages(Base: Constructor) {
return class extends Base {
storageToken?: string;
getImage(formatSlug: string) {
if (!this.storageToken) return null;
return `${formatSlug}${this.storageToken}.png`;
}
};
}
// B - The actual class I want to create
class BaseEntity {
name: string;
constructor(name: string) {
this.name = name;
}
}
export const Entity = withStoredImages(BaseEntity);
const myEntity = new Entity('1', 'Bobby');
// Works
console.log(myEntity.storageToken);
// Works
console.log(myEntity.getImage('original'));
// Good. We can use BaseEntity as a type for our arg
export function printBaseEntity(entity: BaseEntity) {
// Works
console.log(entity.name);
// Error: "Property 'storageToken' does not exist on type 'BaseEntity'."
// Makes sense, since it's before we apply the withStoredImages mixin
console.log(entity.storageToken);
}
// Error: "'Entity' refers to a value, but is being used as a type here. Did you mean 'typeof Entity'?"
// Why can't I use "Entity" as a type for my arg? Let's try using "typeof Entity" then...
export function printEntity(entity: Entity) {
console.log(entity.name);
}
export function printEntity2(entity: typeof Entity) {
// ... Error: "Property 'storageToken' does not exist on type 'typeof (Anonymous class)'?"
console.log(entity.storageToken);
}
Stackblitz: https://stackblitz.com/edit/typescript-rbk3fn?file=entity.model.ts
// Error: "'Entity' refers to a value, but is being used as a type here. Did you mean 'typeof Entity'?"
export function printEntity(entity: Entity) {
console.log(entity.name);
}
Above error makes sense because Entity is not a literal class, it refers to const.
Consider this simplified example:
const foo = class { }
type Foo = foo // error
You are allowed to use class in a type scope only if it was declared as a class declaration and not expression.
In this case you should use typeof Entity. However. typeof Entity represents type of constructor. It means that you are not allowed to use storageToken property, because typeof Entity is a constructor function. You should call it with new. Consider this example:
export function printEntity2(entity: typeof Entity) {
const x = new entity()
x.getImage('hello') // ok
x.storageToken // ok
console.log(x.storageToken); // ok
}
Now it works as expected
One way, though clumsy, is to declare a new type this way
type EntityType = BaseEntity & { storageToken?: string; getImage(formatSlug: string): unknown; };
This type now can be used as parameter of methods printEntity and printEntity2.
function printEntity(entity: EntityType) {
console.log(entity.name);
}
function printEntity2(entity: EntityType) {
console.log(entity.storageToken);
}
However EntityType is not callable, so you can't call new EntityType(...). You still need to create instances with the Entity constructor: const myEntity = new Entity('1', 'Bobby') as EntityType;.
So, an incomplete / clumsy solution... I wonder if both types could be combined together in an elegant way.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With