Signature of call and constructor in Typescript

Author
By Darío Rivera
Posted On in TypeScript

We already saw a standard introduction to the use of Functions in Typescript. In this post, we will see some of the particularities of typescript that surely no other programming language has and that make TypeScript unique. Let's see the call signatures and constructor signatures.

Call signatures

Call signatures or call signatures is the way of calling a special type in TypeScript that is defined from a function type expression. To understand this a little more, let's observe that all functions defined in the traditional way have the type Function.

function sum(a: number, b: number): number {
  return a + b
}

The Function type, when describing all functions, is something very similar to the object type that describes all objects. It is not a good practice to use such a generic type in our code. Observe the following example where a type has been added to the previous function.

type Sum = (a: number, b: number) => number;

let sumFunc: Sum = (a: number, b: number): number => {
    return a + b
}

In this way, we can reuse this type throughout the code without being too generic. We can also define these call signatures in a short or complete way. Observe the next example.

// Shorthand call signature
type Log = (message: string, userId?: string) => void

// Full call signature
type Log = {
  (message: string, userId?: string): void
}

Note: the only difference is that when defined in the complete way, => must be replaced by :.

Another feature of TypeScript taken from JavaScript is that additional properties can be defined for functions (callables). This is possible when defining the call signature in an object.

type Sum = {
    operation: string;
    (a: number, b: number): number;
}

// for some reason, we can only use const here
const sumFunc: Sum = (a: number, b: number) => {
    return a + b;
}

// we define the property after having defined the call signature
sumFunc.operation = 'Suma'

// sumFunc can be used as a function and as an object to access the defined property
console.log('Operation: ' + sumFunc.operation + ', result: ' + sumFunc(4, 5));

Constructor signatures

Constructor signatures or construct signatures are type definitions that can be called with the new operator. The syntax is almost exactly the same as the call signatures except for the mentioned operator.

A constructor signature may look like the following:

type nameableConstructor = {
    new (name: string): Nameable;
}

However, we cannot create a type that meets this specification directly as with any other type.

// this is NOT the way to use this type
let myVar: nameableConstructor ...

Let's carefully observe the following example. It may not be very clear at first, but don't worry, understanding some concepts of any programming language takes some time. After observing the above code, take a look at the explanation below.

interface Nameable {
    name: string
}

class User implements Nameable {
    public name: string;
    public age: number = 0;

    constructor(name: string) {
        this.name = name;
    }
}

type nameableConstructor = {
    new (name: string): Nameable;
}

function buildNameableObject(ctor: nameableConstructor) {
    return new ctor('Bob');
}

buildNameableObject(User);

Note: First of all, if you haven't had contact with interfaces, I recommend taking a look at the article Interfaces in TypeScript

We can start by observing that we have an interface definition that exposes the name property of type string. On the other hand, a class User has been created that implements this interface and receives a name in its constructor that is added to said property of the interface. Nothing new up to here!

What follows is what is really interesting to understand. On the one hand, we have the definition of the constructor signature.

type nameableConstructor = {
    new (name: string): Nameable;
}

This is basically the creation of a type alias that requires a constructor that meets the constructor signature (name: string): Nameable. That is, basically it asks us a class that has that signature in its constructor. Congratulations, we already have that class which is User since it accepts the indicated argument and since it implements the mentioned interface we get an object of that type.

Now, how is it used? For this, we need another function that accepts said type (nameableConstructor) and uses it internally.

function buildNameableObject(ctor: nameableConstructor) {
    return new ctor('Bob');
}

In this way, we can use it as a constructor and create an object of any class that meets the defined signature, such as the User object.

// this statement returns the User object
buildNameableObject(User);

Acerca de Darío Rivera

Author

Application Architect at Elentra Corp . Quality developer and passionate learner with 10+ years of experience in web technologies. Creator of EasyHttp , an standard way to consume HTTP Clients.

LinkedIn Twitter Instagram

Sólo aquellos que han alcanzado el éxito saben que siempre estuvo a un paso del momento en que pensaron renunciar.