James Irwin
James Irwin

Reputation: 1191

Conditionally specify methods/props on base class

Like other JS devs, I somewhat new to thinking about writing things in a static way that the TypeScript compiler can understand. My basic use-case is customizing the actions available on a generic class for accessing a REST endpoint. I can dynamically add methods/props to the base class based on constructor arguments, but doing things that dynamically (understandably) makes the intellisense incorrect. I know I am just thinking about this wrong, and there is a different paradigm for achieving what I want. How can I customize a base class from its children in a way the compiler understands?

Here is what I'm trying:

class Endpoint<T> {
    constructor(
        methods: Array<'GET' | 'CREATE'>
    ) {
        // Conditionally add methods to the endpoint based on what was passed in
        if (methods.includes('GET')) {
            this.get = function get(id: string): Promise<T> {
                return new Promise<T>(resolve => resolve());
            };
        }
        if (methods.includes('CREATE')) {
            this.create = function create(body: T): Promise<T> {
                return new Promise<T>(resolve => resolve());
            };
        }
    }

    public get?: (id: string) => Promise<T>;
    public create?: (body: T) => Promise<T>;
}

class User extends Endpoint<IUser> {
    constructor() {
        super(['GET']);
    }
}

let u = new User();
// u.create will get transpiled away, but still shows up in intellisense

Upvotes: 1

Views: 85

Answers (1)

David Sherret
David Sherret

Reputation: 106690

You might be able to shorten this code and it's not pretty, but the basic idea is to use mixins which were introduced in Typescript 2.2:

class Endpoint<T> {
}

type Constructor<T> = new(...args: any[]) => T;

function Gettable<T, U extends Constructor<Endpoint<T>> = typeof Endpoint>(Base: U) {
   return class extends Base {
        get(id: string): Promise<T> {
            return new Promise<T>(resolve => resolve());
        }
    }
}

function Createable<T, U extends Constructor<Endpoint<T>> = typeof Endpoint>(Base: U) {
    return class extends Base {
        create(body: T): Promise<T> {
            return new Promise<T>(resolve => resolve());
        }
    }
}

class User extends Gettable<IUser>(Endpoint)<IUser> {
}

const u = new User();
u.get("id"); // ok
u.create(); // error, method doesn't exist

Upvotes: 1

Related Questions