I have an object with a function-type member variable, and a setter function which sets any given function-type variable of that same signature to some function:
class Foo {
// This is the function that we want to set to something else
public delegated: (param : string) => void
= () => { console.log("Default value, was not set to anything else."); };
}
// This a function we should be able to set Foo.delegated to, as it has the same signature
function printAllCaps(s : string) : void {
console.log(s.toUpperCase());
}
// This is the function that does the setting. Currently, it doesn't work.
function setDelegateToPrint(delegate : (param : string) => void) {
delegate = printAllCaps;
}
let myFoo : Foo = new Foo();
// We try to set it -- no effect.
setDelegateToPrint(myFoo.delegated);
myFoo.delegated("hello!");
The setDelegateToPrint
function has no effect. I assume that is because myFoo.delegatedFunction
was not passed by-reference, but rather by-copy. Possibly because we didn't pass myFoo
in some way.
How can this be done? It needs to be done without passing myFoo
as a Foo
, because the setter function should not know the Foo
class definition for encapsulation. It can only know the bare minimum required information: there is an object, and the object has a member function of a given signature.
Note: this code not fullfilling the needed task can easily be checked by copy-pasting the entire code into TypeScript playground and pressing "run".
"This needs to be done without passing myFoo itself" - then, you can't.
What you could do is pass myFoo, but make setDelegateToPrint
generic and accept one of its keys.
I changed a bit your code:
// This class has not been changed
class Foo {
public delegatedFunction: (param: string) => void = () => {
console.log("Default value, was not set to anything else.");
};
}
// Here I'm just ensuring the function is the same type
const printAllCaps: Foo["delegatedFunction"] = (s) => {
console.log(s.toUpperCase());
};
// Here I added some type information so that the
// second parameter has to be a key of the first parameter
//
// NOTE: this can be improved, for example passing a third
// parameter which is the function (printAllCaps) and it
// must be of the same type of the method you're overriding.
// More on this later.
function setDelegateToPrint<O, K extends keyof O>(
obj: O[K] extends Foo["delegatedFunction"] ? O : never,
key: K
) {
obj[key] = printAllCaps;
}
let myFoo: Foo = new Foo();
// Here I'm passing the key of the method to override, instead of passing the method itself
setDelegateToPrint(myFoo, "delegatedFunction");
myFoo.delegatedFunction("hello!");
This can be improved by passing a third parameter to setDelegateToPrint
and making it even more generic:
function setDelegateToPrint<O, K extends keyof O>(
obj: O,
key: K,
overrideFn: O[K]
) {
obj[key] = overrideFn;
}
setDelegateToPrint(myFoo, "delegatedFunction", printAllCaps);
FOOTNOTE:
be aware that you're mutating an object from a scope outside the function and it is not advisable at all, it will be hard to debug for sure. Do this only if you're sure you cannot avoid that.