I have an app using some generic classes with the Mixin pattern, and am trying to implement a property getter that's the same for 90% of users of the mixin, but not quite all...
I was surprised to find:
- It's fine to define abstract methods, implement them in the mixin, and override them in a child class
- It's fine to define new property getters in the mixin and override them in the child class
- But, trying to define abstract property getters, implement them in the mixin, and override them in the child class - raises an error?
and found an unexpected issue when I tried to override a property getter defined in the mixin on a child class.
Below is a small code snippet that tries to demonstrate all 3 behaviours - can anybody help me understand why point #3 is an error and whether there's a good way to fix it? I'd like to keep the safety of any children of TextThing
being forced to implement the abstract property...
/**
* Basic interface objects should implement
*/
export interface ITextThing<T> {
dataSource: T;
html(): string;
get text(): string;
}
/**
* Abstract base class with partial implementation
*/
export abstract class TextThing<T> implements ITextThing<T> {
dataSource: T;
constructor(data: T) {
this.dataSource = data;
}
abstract html(): string;
abstract get text(): string;
}
type AbstractOrConcreteConstructor<T> = (new (...args: any[]) => T) | (abstract new (...args: any[]) => T);
/**
* A mixin with extra functionality
*/
export function WithCounter<T extends AbstractOrConcreteConstructor<{}>>(SuperClass: T) {
return class extends SuperClass {
_counter: number;
constructor(...args: any[]) {
super(...args);
this._counter = 0;
}
get count(): number {
return this._counter;
}
get text(): string {
return `[${this._counter}]`;
}
};
}
/**
* The problem class: Using the mixin...
*/
export class TextWithCounter<TData> extends WithCounter(TextThing)<TData> {
constructor(data: TData, initialCount = 0) {
super(data);
this._counter = initialCount;
}
// Overriding a getter defined in the mixin seems fine...
override get count(): number {
return this._counter + 1;
}
// Overriding a base class abstract function implemented in mixin seems fine...
html(): string {
return `<p><strong>${this.count}: </strong>${this.dataSource}</p>`;
}
// But base class abstract getter implemented in the mixin throws an error?
// > 'text' is defined as a property in class 'WithCounter<typeof TextThing>.(Anonymous class)
// & TextThing<TData>', but is overridden here in 'TextWithCounter<TData>' as an
// accessor.ts(2611)
override get text(): string {
return `[${this.count}]: ${this.dataSource}`;
}
}
This project is currently using TypeScript v4.5.4, but could probably upgrade if it'd help? I can't quite tell if this is a bug/limitation or an issue with our code...