Kousha
Kousha

Reputation: 36189

Dynamic Typescript type for methods passed in

Say I have

interface User {
    name: string;
    age: number;
}

interface Car {
    year: number;
    model: string;
}

interface Action<T> {
    assign<K extends keyof T>(key: K, value: T[K]): void;
}

This allows me to do:

const userActions: Action<User> = ...;
const carActions: Action<Car> = ...;

userActions.assign('age', 1); // all good
userActions.assign('foo', 2); // error that `foo` does not exist
userActions.assign('age', 'foo'); // error that type string is not assignable to age

carActions.assign(...); // same behavior for car

Now I want to create auxiliary methods that can be passed to assign, for example:

const logAndAssign = (key, value): void;

And I want to be able to do

userActions.assign(logAndAssign('age', 1));
// etc

And so I want these aux methods logAndAssign to get the type passed to them. How can I achieve this?

Upvotes: 3

Views: 83

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249506

You can't call a function with a single argument directly, you could use apply but apply call would not be type safe and calling logAndAssign would imply you passing the type arguments explicitly:

const logAndAssign = function <T, K extends keyof T>(key: K, value: T[K]): [K, T[K]] {
    console.log(`${key} ${value}`);
    return [key, value];
};
userActions.assign.apply(userActions, logAndAssign<User, 'age'>('age', 1));

A better solution would be replace the assign function on Action and then restore it:

function withLogging<T>(a: Action<T>, doStuff: (a: Action<T>) => void) {
    let oldAssign = a.assign;
    // Replace the assign function with a logging version that calls the original
    a.assign = function <K extends keyof T>(key: K, value: T[K]): void {
        console.log(`${key} ${value}`);
        oldAssign.call(this, key, value);
    };
    try {
        doStuff(a);
    } finally {
        //Restore the original assign 
        a.assign = oldAssign;
    }
}
// Single call
withLogging(userActions, u => u.assign('age', 10));
// Multiple calls
withLogging(userActions, u => {
    u.assign('age', 10);
    u.assign("name", 'd');
});

Upvotes: 2

Related Questions