3

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?

1
  • AFAIK, as you've described it, this won't be possible (even in plain js). Since a function can only return a single value, there's no way for the logAndAssign call to fill in both arguments to .assign, it would only be able to fill in the first. Commented Jan 17, 2018 at 2:46

1 Answer 1

2

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');
});
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.