Home > Enterprise >  How to extends abstract class from template in TypeScript?
How to extends abstract class from template in TypeScript?

Time:04-26

I have an architecture like that and I want to extends my TemplateService with a template but actually I have an error on it. Is there a way to extends an abstract class from a template ?

// Error message
ReferenceError: Service is not defined
abstract class TemplateService<Service> extends Service
                                               ^
// My code
abstract class TemplateService<Service> extends Service { // Cannot find name 'Service'.
    constructor() {
        const name = "Hello world !";

        super(name);
    }

    public getJob(): string {
        return "Job";
    }

    protected getAge(): number {
        return 25;
    }
}

class ImportedService {
    constructor(private _name: string) {}

    public getName() {
        return this._name;
    }
}

class MyService extends TemplateService<ImportedService> {
    constructor() {
        super();
    }

    public fooMethod(): void {
        ...
    }
}

CodePudding user response:

It is a category error to write

abstract class TemplateService<Service> extends Service {}

TypeScript's static type system, including generic type parameters, is erased from the emitted JavaScript that actually runs. In the above, abstract and <Service> are part of the static type system, while the rest is valid JavaScript (at least for modern versions of JavaScript). Your code as written would be emitted to something like

class TemplateService extends Service {}

which hopefully shows the problem. Unless there happens to be a class constructor named Service in scope, this would be a runtime error. You can't subclass a type; you need to subclass a constructor value.


TypeScript has some support for subclassing an arbitrary constructor value via the concept of mixins. Instead of writing TemplateService as a class, you make it a class factory function that accepts a class constructor as an input. Conceptually it would look like

// don't write this, it's not valid TS
function TemplateService<C extends new (name: string) => object>(ctor: C) {
    return class extends ctor {  // error!
// ------> ~~~~~
        constructor() {
            const name = "Hello world !";
            super(name);
        }
        public getJob(): string {
            return "Job";
        }

        protected getAge(): number {
            return 25;
        }
    };
}

And you'd use it like

class ImportedService {
    constructor(private _name: string) { }

    public getName() {
        return this._name;
    }
}

class MyService extends TemplateService(ImportedService) {
    constructor() {
        super();
    }

    public fooMethod(): void {
        console.log(this.getName());
        console.log(this.getJob());
        console.log(this.getAge())
    }
}

Unfortunately, TypeScript's mixin support assumes that you are going to just pass the constructor parameters through, so the above TemplateService implementation gives compiler errors. There are open issues about this, such as microsoft/TypeScript#37142. For now all I could say is to work around it by using several type assertions to trick the compiler into allowing the mixin and into to giving it the correct type.

For the particular example code here, it could look like:

function TemplateService<C extends new (name: string) => object>(ctor: C) {
    const c = class extends (ctor as new (...args: any) => object) {
        constructor() {
            const name = "Hello world !";
            super(name);
        }
        public getJob(): string {
            return "Job";
        }

        protected getAge(): number {
            return 25;
        }
    };

    return c as {
        new(): InstanceType<C> & InstanceType<typeof c>;
    }
}

Now the compiler believes that TemplateService(ImportedService) is a no-arg constructor which produces instances of a combination (via intersection) of ImportedService and the type of the anonymous class expression with a getJob() method and protected getAge() method.

Playground link to code

  • Related