Home > OS >  Is it possible to use type in a function called from parent function in Typescript?
Is it possible to use type in a function called from parent function in Typescript?

Time:01-03

I want to call a function inside another function and use its type parameter as "default" type when calling child function.

Is it possible in Typescript?

// Parent interface
interface IParent {
    id: number;
    name: string;
}

// Child interface with foreign key
interface IChild {
    id: number;
    name: string;
    ParentId: number;
}

// Parent function declaration:
function select<T>(query: {
    select: string,
    join: string,
}) {
    return `${query.select} ${query.join}`;
}

// Child function declaration (F type is optional):
function buildJoin<T, F = Record<any, any>>(foreignKey: keyof T, otherColumn: keyof F): string {
    return 'JOIN f on t.foreignKey = f.otherColumn';
}

// Strong typing:
select<IParent>({
    select: 'select * from Parent',
    join: buildJoin<IChild, IParent>('ParentId', 'id'), // explicitly typed "ParentType"
});

// Requested behaviour:
select<IParent>({
    select: 'select * from Parent',
    join: buildJoin<IChild>('ParentId', 'id'), // if 2nd type parameter omitted it should be taken from parent function
});

Typescript playground link

CodePudding user response:

You're trying to infer a type argument to buildJoin based on where it's called, which you can't do. So in that sense, the answer to your question is "no."

As an alternative, you might write a fluent interface in which select returns an object with a join method, which can then inherit the type parameter's value. Here's a rough example (not intended to be an actual robust implementation [though it does work for this limited example], just an example):

// Parent interface
interface IParent {
    id: number;
    name: string;
}

// Child interface with foreign key
interface IChild {
    id: number;
    name: string;
    ParentId: number;
}

// Parent function declaration:
function select<ParentType>(select: string) {
    return {
        query: select,
        join<ChildType>(foreignKey: keyof ChildType, otherColumn: keyof ParentType) {
            this.query  = "\nJOIN f on t.foreignKey = f.otherColumn";
            return this;
        },
        build() {
            return this.query;
        },
    };
}

// Strong typing:
const sql = select<IParent>("select * from Parent")
    .join<IChild>("ParentId", "id")
    .build();

Playground example

CodePudding user response:

There is another option relying on tagged primitive types, currying and function composition.

const select = <Parent>(select: string) =>
    select as string & { _parent: Parent };

const join = <Child>(foreignKey: keyof Child) => <Parent>(otherColumn: keyof Parent) =>
    (select: string & { _parent: Parent }) =>
        `${select}\nJOIN f on t.foreignKey = f.otherColumn`;

You can use it like so

import { pipe } from 'fp-ts/lib/function'

const ok1 = pipe(
    select <IParent>("select * from Parent"),
    join <IChild>("ParentId") ("id")
)

const ok2 = pipe(
    select <IParent>("select * from Parent"),
    join <IChild>("ParentId") <IParent>("id")
)

plyaground

CodePudding user response:

yes it is possible. By calling the super() method in the constructor method, we call the parent's constructor method and gets access to the parent's properties and methods. Inheritance is useful for code reusability: reuse properties and methods of an existing class when you create a new class.

  • Related