Bart van den Burg
Bart van den Burg

Reputation: 2344

Typescript: type one parameter based on the other

See this playground: Playground

export interface IProduct {
    productId: number;
}

export interface ICompany {
    companyId: number;
}

function click(type: 'product', entity: IProduct): string;
function click(type: 'company', entity: ICompany): string;
function click<T>(type: 'product' | 'company', entity: T) {
    if (type === 'product') {
        return 'product ' + entity.productId;
    } else if (type === 'company') {
        return 'company ' + entity.companyId;
    }

    throw new Error('wrong input');
}

click('product', { productId: 123 });

How do I make typescript understand, that if the first if evaluates true, then the second argument is an IProduct?

Upvotes: 0

Views: 75

Answers (2)

DoronG
DoronG

Reputation: 2663

A working solution (but not a good one):

function click(type: 'product', entity: IProduct): string;
function click(type: 'company', entity: ICompany): string;
function click<T extends IProduct | ICompany>(type: 'product' | 'company', entity: T) {
    if (type === 'product' && assertType(type, entity)) {
        return 'product ' + entity.productId;
    } else if (type === 'company' && assertType(type, entity)) {
        return 'company ' + entity.companyId;
    }

    throw new Error('wrong input');
}

function assertType(type: 'product', entity: IProduct | ICompany): entity is IProduct;
function assertType(type: 'company', entity: IProduct | ICompany): entity is ICompany;
function assertType(type: 'product' | 'company', entity: IProduct | ICompany): entity is IProduct | ICompany {
    return entity && type === "product" || type === "company";
}

Playground Link

A much better solution (in my mind):

function click(entity: IProduct | ICompany) {
    // here: (parameter) entity: IProduct | ICompany

    if ("productId" in entity) {
        // here: (parameter) entity: IProduct
        return `product ${entity.productId}`;

    } else if ("companyId" in entity) {
        // here: (parameter) entity: ICompany
        return `company ${entity.companyId}`;
    }

    throw new Error('wrong input');
}

Playground Link

Upvotes: 0

Aleksey L.
Aleksey L.

Reputation: 37918

You could type arguments as union of tuples, but it is ugly because destructuring can't be used (typescript will loose type relation):

function click(type: 'product', entity: IProduct): string;
function click(type: 'company', entity: ICompany): string;
function click(...args: ['product', IProduct] | ['company', ICompany]) {
    if (args[0] === 'product') {
        return 'product ' + args[1].productId;
    } else if (args[0] === 'company') {
        return 'company ' + args[1].companyId;
    }

    throw new Error('wrong input');
}

Playground

Upvotes: 2

Related Questions