Murolem
Murolem

Reputation: 821

How to do combine multiple classes in one in TypeScript?

Basically, I want to be able to combine any number of classes, which, for example, will be defined in the same way as mixinHobbies or mixinNotes, and make TypeScript and IntelliSense not go crazy.

The order of mixins can be constant, but the number of ones that will be applied is varying.

function mixinHobbies(Base: PersonCtor, hobbies: string[]) {
    return class extends Base {
        hobbies = hobbies;
    }
}

function mixinNotes(Base: PersonCtor, notes: string[]) {
    return class extends Base {
        notes = notes;
    }
}


interface PersonCtor {
    new (name: string, age: number): IPerson
}

interface IPerson {
    name: string;
    age: number;
}

class Person implements IPerson {
    name: string;
    age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

const PersonWithHobbies = mixinHobbies(Person, ['drawing', 'reading']);
const PersonWithHobbiesAndNotes = mixinNotes(PersonWithHobbies, ['killlisted']);

const alex = new PersonWithHobbiesAndNotes('Alex', 18);

console.log(alex.hobbies);

console.log(alex.hobbies); throws Property 'hobbies' does not exist on type '(Anonymous class)'. IntelliSense also doesn't see the hobbies field.

Tho if executed, logs fine.

Something like this would be just perfect:

function mixinPerson() {
    return new class {
        result = DefaultPerson;

        Hobbies(hobbies: string[]) {
            this.result = class extends this.result {
                hobbies = hobbies;
            }
            return this;
        }

        Notes(notes: string[]) {
            this.result = class extends this.result {
                notes = notes;
            }
            return this;
        }
    }
}


interface PersonCtor {
    new (name: string, age: number): IPerson
}

interface IPerson {
    name: string;
    age: number;
}

class DefaultPerson implements IPerson {
    name: string;
    age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

const SuperPerson = mixinPerson()
.Hobbies(['drawing', 'reading'])
.Notes(['killlisted'])
.result;

const alex = new SuperPerson('Alex', 18);

console.log(alex.hobbies);

Upvotes: 2

Views: 1753

Answers (1)

jcalz
jcalz

Reputation: 329943

I think you just need your mixins to be generic in the type of the Base constructor, so that the compiler does not widen it to typeof Person and forget about any additional structure:

function mixinHobbies<C extends new (...args: any) => Person>(Base: C, hobbies: string[]) {
  return class extends Base {
    hobbies = hobbies;
  }
}

function mixinNotes<C extends new (...args: any) => Person>(Base: C, notes: string[]) {
  return class extends Base {
    notes = notes;
  }
}

You can see that it works as desired:

const alex = new PersonWithHobbiesAndNotes('Alex', 18);
console.log(alex.hobbies); // no compiler error

Playground link to code

Upvotes: 3

Related Questions