Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Build a function object with properties in TypeScript

People also ask

How do I create a function object in TypeScript?

To declare a function with an object return type, set the return type of the function to an object right after the function's parameter list, e.g. function getObj(): {name: string;} {} . If the return type of the function is not set, TypeScript will infer it.

How do I add a function to properties TypeScript?

To add or attach properties to functions in TypeScript, one of the ways to do is to make a type alias and write the function signature (aka function call signature) and the property with its type you need to attach to the function inside the type alias .

How do you call a function with parameters in TypeScript?

In functions, parameters are the values or arguments that passed to a function. The TypeScript, compiler accepts the same number and type of arguments as defined in the function signature. If the compiler does not match the same parameter as in the function signature, then it will give the compilation error.

Can an object have a function as a property?

In JavaScript, functions are called Function Objects because they are objects. Just like objects, functions have properties and methods, they can be stored in a variable or an array, and be passed as arguments to other functions.


Update: This answer was the best solution in earlier versions of TypeScript, but there are better options available in newer versions (see other answers).

The accepted answer works and might be required in some situations, but have the downside of providing no type safety for building up the object. This technique will at least throw a type error if you attempt to add an undefined property.

interface F { (): any; someValue: number; }

var f = <F>function () { }
f.someValue = 3

// type error
f.notDeclard = 3

This is easily achievable now (typescript 2.x) with Object.assign(target, source)

example:

enter image description here

The magic here is that Object.assign<T, U>(t: T, u: U) is typed to return the intersection T & U.

Enforcing that this resolves to a known interface is also straight-forward. For example:

interface Foo {
  (a: number, b: string): string[];
  foo: string;
}

let method: Foo = Object.assign(
  (a: number, b: string) => { return a * a; },
  { foo: 10 }
); 

which errors due to incompatible typing:

Error: foo:number not assignable to foo:string
Error: number not assignable to string[] (return type)

caveat: you may need to polyfill Object.assign if targeting older browsers.


TypeScript is designed to handle this case through declaration merging:

you may also be familiar with JavaScript practice of creating a function and then extending the function further by adding properties onto the function. TypeScript uses declaration merging to build up definitions like this in a type-safe way.

Declaration merging lets us say that something is both a function and a namespace (internal module):

function f() { }
namespace f {
    export var someValue = 3;
}

This preserves typing and lets us write both f() and f.someValue. When writing a .d.ts file for existing JavaScript code, use declare:

declare function f(): void;
declare namespace f {
    export var someValue: number;
}

Adding properties to functions is often a confusing or unexpected pattern in TypeScript, so try to avoid it, but it can be necessary when using or converting older JS code. This is one of the only times it would be appropriate to mix internal modules (namespaces) with external.


So if the requirement is to simply build and assign that function to "f" without a cast, here is a possible solution:

var f: { (): any; someValue: number; };

f = (() => {
    var _f : any = function () { };
    _f.someValue = 3;
    return _f;
})();

Essentially, it uses a self executing function literal to "construct" an object that will match that signature before the assignment is done. The only weirdness is that the inner declaration of the function needs to be of type 'any', otherwise the compiler cries that you're assigning to a property which does not exist on the object yet.

EDIT: Simplified the code a bit.


Old question, but for versions of TypeScript starting with 3.1, you can simply do the property assignment as you would in plain JS, as long as you use a function declaration or the const keyword for your variable:

function f () {}
f.someValue = 3; // fine
const g = function () {};
g.someValue = 3; // also fine
var h = function () {};
h.someValue = 3; // Error: "Property 'someValue' does not exist on type '() => void'"

Reference and online example.


I can't say that it's very straightforward but it's definitely possible:

interface Optional {
  <T>(value?: T): OptionalMonad<T>;
  empty(): OptionalMonad<any>;
}

const Optional = (<T>(value?: T) => OptionalCreator(value)) as Optional;
Optional.empty = () => OptionalCreator();

if you got curious this is from a gist of mine with the TypeScript/JavaScript version of Optional


As a shortcut, you can dynamically assign the object value using the ['property'] accessor:

var f = function() { }
f['someValue'] = 3;

This bypasses the type checking. However, it is pretty safe because you have to intentionally access the property the same way:

var val = f.someValue; // This won't work
var val = f['someValue']; // Yeah, I meant to do that

However, if you really want the type checking for the property value, this won't work.