Typescript, as a superset of JavaScript, enhances JavaScript’s capabilities, including the way we define object structures. We’re going to delve into a common misconception: What’s the difference between method signatures and function property definitions in TypeScript interfaces? We’ll break it down with some examples, and explain the subtle differences between these two ways of declaring a method in an interface:

1interface MyInterface {
2    myMethod(): void; // method
3    myMethod: () => void; // function
4}

Defining methods in interfaces

In Object-Oriented Programming (OOP), methods are the behaviours classes. TypeScript interfaces allow us to specify a contract for these methods. When we define a method in an interface using myMethod(): ReturnType;, we’re telling any class that implements this interface, “You need to have a method of this name, these arguments, and returns this type”.

By adhering to the interface’s contract, we get these benefits:

  • Consistency: Every class implementing the interface will have this method.
  • ‘This’ Context: The method gets access to the class instance it belongs to.
  • Overriding: Child classes can modify these methods while still conforming to the interface.
1interface MyInterface {
2  myMethod(): void;
3}
4
5class MyClass implements MyInterface {
6  myMethod() {
7    console.log("Hello from myMethod()"); // example implementation
8  }
9}

Here, MyClass will always have a method myMethod defined in the MyInterface interface. Typescript will not compile until this method is implemented.

Defining functions in interfaces

TypeScript also lets us define properties that are functions. We write it like this: myFunction: () => ReturnType;. It basicaly means that myFunction must be some function.

1interface MyInterface {
2    myFunction: () => void; // function
3}
  • Flexibility: You can switch out these functions whenever you like.
  • Beware of ‘This’: The function might lose track of which class it belongs to unless you tell it explicitly.
  • First class citizens: You can pass these functions around like any other variable.
1interface MyInterface {
2  myFunction: () => void;
3}
4
5class MyClass implements MyInterface {
6  myFunction = () => {
7    console.log("Hello from my function"); // example function implementation
8  };
9}

This is the problem

The tricky part with function properties is the this keyword. It doesn’t always point to the class instance. It’s a very unexpected and unpredictable behaviour if you come frome other object-oriented languages.

In regular functions (also known as function declarations or expressions), the value of this is determined by how the function is called. This is a fundamental aspect of JavaScript functions that can sometimes lead to unexpected behavior, particularly in object-oriented scenarios.

When you define a regular function as a method inside a class (or an object), and you call that method directly from an instance of the class, this will refer to the instance itself. However, if the method gets detached from its object - for instance, if you pass it as a callback - this will no longer refer to the original object.

 1class MyClass {
 2  name = "MyClass";
 3
 4  myFunction() {
 5    console.log(this.name);  // 'this' refers to the instance of MyClass
 6  }
 7}
 8
 9const myInstance = new MyClass();
10const func = myInstance.myFUnction;
11func();  // 'this' is undefined or global object, not myInstance

Arrow functions, introduced in ES6, handle this differently. They do not have their own this context but inherit this from the surrounding code where they are defined. This feature makes them particularly useful in scenarios where you want to maintain the context without having to bind it manually.

In the context of TypeScript interfaces and classes, using arrow functions as methods means this always refers to the class instance, regardless of how the function is passed around or used.

 1class MyClass {
 2  name = "MyClass";
 3
 4  myArrowFunction = () => {
 5    console.log(this.name);  // 'this' always refers to the instance of MyClass
 6  }
 7}
 8
 9const myInstance = new MyClass();
10const func = myInstance.myArrowFunction;
11func();  // 'this' correctly refers to myInstance

Conclusion

Now that we have gone through the methods and function, you are now prepared to bring an informed decision. You can choose which one to use depending on the situation. In most cases, it is going to be methods. Though, sometimes methods need to be passed around, and that would be a sign to define them as functions.