Eric Ipsum
Eric Ipsum

Reputation: 745

any class as function argument in typescript

Suggestion needed: As a newbie, I have class like below in typescript:

export declare class SampleOne extends Setting {
    getValue(): Promise<boolean>;
    setValue(value: boolean): Promise<void>;
}

And

export declare class SampleTwo extends Setting {
    getValue(): Promise<boolean>;
    setValue(value: boolean): Promise<void>;
}

Now i want a helper function where i can pass either SampleOne or SampleTwo like below:

async function getObj(title: string, categories: string, cname: Setting) {

    let obj = await et.findSetting(title,categories) as cname;// i want to pass either **SammpleOne** or **SampleTwo** class.
    return obj;
}

Function call would be like

getObj(title, categories, SampleOne)

getObj(title, categories, SampleTwo)

I am strangling to created this helper function.How it should be looks like in typescript??

Thanks in advance.

Upvotes: 0

Views: 316

Answers (4)

Lesiak
Lesiak

Reputation: 25966

I would go with Convenience generic pattern

async function getObj<T extends Setting>(title: string, categories: string): Promise<T> {
    let obj = await et.findSetting(title,categories) as T;
    return obj;
}

or, dropping the unncecessary async:

function getObj<T extends Setting>(title: string, categories: string): Promise<T> {
    return et.findSetting(title,categories) as Promise<T>;
}

Usage:

const s1 = getObj<SampleOne>('a', 'b');

Pros:

  • can be used if you model Setting as a class
  • can be used if you model Setting as an interface

Cons:

  • no runtime check (which may or may not be a problem)

If you need runtime check and have settings modeled as classes, you can use Type (as coded by Angular folks):

interface Type<T> extends Function { new (...args: any[]): T; }

async function getObj<T extends Setting>(
    title: string, categories: string, klass: Type<T>): Promise<T> 
{
   let obj = await et.findSetting(title,categories);
   if (klass.prototype.isPrototypeOf(obj)) {
    return obj as T; 
   } else {
     throw new Error(`Expected ${klass.name} but got ${obj.constructor.name}`)
   }
   
}

const s1 = getObj('a', 'b', SampleOne);
s1.then(x => console.log(x))
  .catch(e => console.log(e));

Upvotes: 1

I believe you can just apply this constraint T extends typeof Setting to generic:

class Setting {
    getValue(): Promise<boolean> { return Promise.resolve(true) };
    setValue(value: boolean): Promise<void> { return Promise.resolve() };
}


class SampleOne extends Setting {
    getValue(): Promise<boolean> { return Promise.resolve(true) };
    setValue(value: boolean): Promise<void> { return Promise.resolve() };
}

class SampleTwo extends Setting {
    getValue(): Promise<boolean> { return Promise.resolve(true) };
    setValue(value: boolean): Promise<void> { return Promise.resolve() };
}

class DontAllowed { }

type AllowedClasses = typeof SampleOne | typeof SampleTwo

class SampleThree { // does not extends Setting
    getValue(): Promise<boolean> { return Promise.resolve(true) };
    setValue(value: boolean): Promise<void> { return Promise.resolve() };
}

function getObj<T extends typeof Setting>(cname: T) {
    return cname
}

const result = getObj(SampleOne) // ok
const result2 = getObj(SampleTwo) // ok
const result3 = getObj(DontAllowed) // expected error

In above example, I assumed that every class which extends Setting is allowed. If you want to allow anly two classes, you can do this thing:


type AllowedClasses = typeof SampleOne | typeof SampleTwo

function getObj<T extends AllowedClasses>(cname: T) {
    return cname
}

Playground

I don't think that it is necessary to use type assertion (as operator) in such cases.

Upvotes: 1

lbsn
lbsn

Reputation: 2412

Use generics.

async function getObj<T>(title: string, categories: string) {

    let obj = await et.findSetting(title,categories) as T;
    return obj;
}

Then you can call the function with the desired type:

getObj<SampleOne>('foo', 'bar');
getObj<SampleTwo>('foo', 'bar');

Upvotes: 0

georg
georg

Reputation: 214969

You can make getObj generic by the type of Setting and parametrize the last argument as a constructor of T:

type Class<T> = new (...args: any) => T;

async function getObj<T extends Setting>(
    title: string, categories: string, klass: Class<T>): Promise<T> 
{
    return .... as  T
}

Upvotes: 1

Related Questions