TypeScript Interfaces and JavaScript Higher-Order Functions
Photo by RODNAE Productions from Pexels

TypeScript Interfaces and JavaScript Higher-Order Functions

Code can be said to be decoupled when your components are designed in such a way that they don’t depend on the concrete implementations of others. Components are loosely coupled when dependencies between them are enforced by a type of code agreement or contract to make the communication lines between them are as simple as possible.

What follows is an explanation of how we can achieve this with both interfaces and higher-order functions.

An interface is a structure that defines the contract in your application. Classes that are derived from an interface must follow the structure provided by their interface.

A higher-order function is a function that receives a function as an argument or returns the function as output.

An interface is defined with the keyword interface and it can include properties and method declarations:

1//ProductRepository.d.ts 2export interface ProductRepository { 3 createProduct: (name: string, price: number) => boolean 4 getProduct: (id:number) => Product 5}

.d.ts files are declaration files that contain only type information. These files don't produce .js outputs; they are only used for type checking.

In the above example, the ProductRepository interface includes two method declarations, createProduct and getProduct using an arrow function that includes parameters and return type.

An interface in TypeScript can also be used to define a type

1//Product.d.ts 2export type Product = { 3 name: string 4 price: number 5}
1//Product.d.ts 2export interface Product { 3 name: string 4 price: number 5}

Let’s use this a class implementation:

ProductRepositoryImpl

Using TypeScript and VS Code, we immediately notice that the interface does its job and informs us that the implementation is incomplete. The 2 methods are missing.

Let’s fix this.

ProductRepositoryImpl

Let’s see how we can use the interface to write the ProductRepository in a more functional way using a higher-order function.

Because interfaces can be used for custom types, it is used for declaring the function return type:

ProductRepositoryImpl

ProductRepositoryImpl

Let’s fix the code

ProductRepositoryImpl

Let’s try this in plain JS. JavaScript inheritance is based on objects, not classes so we can’t use interface inheritance in JS. We have no choice but to use a higher-order function.

So let’s copy and paste this code into a .js file and see what happens:

ProductRepositoryImpl

Ok, so we have to remove the ProductRepository annotation and replace it with a JSDoc annotation.

ProductRepositoryImpl

and we’re back to where we were, just this time in plain JS. Great if you want to have the power of IntelliSense and type checking without the compilation and configuration overhead of TypeScript

ProductRepositoryImpl

Let’s now complete the code to remove the errors.

ProductRepositoryImpl

Conclusion:

When working with TypeScript you have the choice of interfaces or higher-order functions to enforce structure and decoupling.

The same can be done for JavaScript with higher-order functions using JSDoc and TypeScript type declaration files.