TypeScript Make the function parameter more maintainable by using interface

eye-catch JavaScript/TypeScript

Many programming languages support interface keyword. TypeScript also supports the feature. This is one of the important concepts to know to work as a professional programmer.

TypeScript offers the same feature as other languages but it can also be used when we want to define a parameter list that a function requires.

Interface as a function parameter list can make the code more readable and maintainable. So let’s see the advantages in this article.

Sponsored links

What is an interface

Interface has only a definition. It doesn’t have any concrete implementation. A user simply relies on the interface to use the module/system without knowing the internal logic.

For example, if there is an audio module, the interface looks something like this.

interface Audio {
    start(): void;
    resume(): void;
    stop(): void;
    setAudio(path: string): void;
}

You set an audio file but you don’t know how the audio file is handled. It depends on the file type, mp3, mp4, wav, or something else.

If each developer uses the interface and adds the concrete implementation into the function, you as a client-side don’t have to consider how the audio file is handled in the function.

You can force a developer to implement the function to make it work. That’s the nice point to use interface.

This is the common usage but interface can be used as an argument list definition in TypeScript as I mentioned above. This is an example.

type MyType = {
    name: string;
    age: number;
}
function funcWithType(args: MyInterface) {
    return `I'm ${args.name}. I'm ${args.age} years old.`;
}

Let’s see why it makes the code maintainable and what issues arise without using it.

Sponsored links

What issues arise when not using an Interface for the function arguments

The order cannot change easily

When creating a function in TypeScript, it looks like this below.

function func(name: string, age: number) {
    return `I'm ${name}. I'm ${age} years old.`;
}
console.log(func("Yuto", 35));
// I'm Yuto. I'm 35 years old.

Many functions require arguments. If the arguments are defined separately, we have to specify them one by one. If we want to change the order of the argument, we have to make the change in as many lines as function calls.

// change the order of the arguments
function func(age: number, name: string) {
    return `I'm ${name}. I'm ${age} years old.`;
}
// change the order
func(35, "Yuto");

Need to specify additional arguments at all function calls

Likewise, when we want to add additional arguments to the function, we have to add the argument to the function in the right position.

// isAdmin is added
function func(name: string, age: number, isAdmin: boolean) {
    let msg = `I'm ${name}. I'm ${age} years old.`;
    if(isAdmin) {
        msg += "I have admin right."
    }
    return msg;
}

// true is set to isAdmin
func("Yuto", 35, true);

It’s no problem when we use the function for the first time but if the parameters are passed by an object, we need to pass them one by one.

function func(name: string, age: number) {
    return `I'm ${name}. I'm ${age} years old.`;
}

const person = {
    name: "Yuto",
    age: 35,
    isAdmin: true,
    job: "Programmer",
};
func(person.name, person.age);

When we add isAdmin argument, we also need to update the caller side.

// add isAdmin
function func(name: string, age: number, isAdmin: boolean) {
    // ...
}

const person = {
    name: "Yuto",
    age: 35,
    isAdmin: true,
    job: "Programmer",
};
// 3rd argument is added
func(person.name, person.age, person.isAdmin);

person object contains all required parameters but we need to set them one by one. If the function is called at many places, we have to fix them all.

Not readable if it has too many arguments

This is the main reason why I want to use an interface for function argument definition. Let’s define the following function that has 4 arguments.

function func(name: string, age: number, isAdmin = false, interests?: string[]) {
    ...
}
func("Yuto", 35, false, ["Guitar", "Music"])

You might be able to remember which argument is what as long as the number of arguments is small.

But what if the function has 10 arguments? If the number of arguments is big, it’s almost impossible to remember which position requires which value.

You definitely think it’s more readable if we can have the parameter name when trying to call the function. Especially, if the function requires only boolean value, it’s hard to follow which boolean is what for.

function func(isMember: boolean, isCached: boolean, hasCard: boolean, hasAccessToService1: boolean){
    ...
}

// Hmm... It's hard to follow...
func(false, false, true, false);

Since IntelliSense doesn’t show the parameter name, we should somehow improve this readability.

Defining an interface for the better code

Requires an object that contains the necessary arguments

If the arguments are defined as an object, we can change the order, pass the whole object that contains the required arguments without any change.

If we set the argument value in the function call directly, the argument name needs to be written. The IntelliSense helps us know which argument the function requires. We don’t have to go the definition. It is more readable.

function func2(args: { name: string, age: number }) {
    return `I'm ${args.name}. I'm ${args.age} years old.`;
}
// set the argument name (property) too
func2({ age: 35, name: "Yuto" });

const person = {
    name: "Yuto",
    age: 35,
    isAdmin: true,
    job: "Programmer",
};
func2(person);

person object contains unnecessary properties but they are just ignored.

Define an interface for the arguments

If we need to use the same definition in other places, we must write it again. It’s smarter to use an interface here.

interface MyInterface {
    name: string;
    age: number;
}
function funcWithInterface(args: MyInterface) {
    return `I'm ${args.name}. I'm ${args.age} years old.`;
}

We can add new arguments to the interface but we don’t have to change the function call side if the passed object already has them.

interface MyInterface {
    isAdmin: boolean; // new arg
    job: string; // new arg
    name: string;
    age: number;
}
// The order of the properties is changable
const person = {
    name: "Yuto",
    age: 35,
    isAdmin: true,
    job: "Programmer",
};
// we don't have to change the function call as far as the object contains all necessary info
funcWithInterface(person);

This is powerful if we use a function to generate an object to be passed to the target function.

function createArgumentList(): MyInterface {
    // long steps here
    // ...
    return {
        // desired properties here
    };
}

// file1
const args = createArgumentList();
funcWithInterface(args);

// file2
const args = createArgumentList();
funcWithInterface(args);

A function might be called in several places but we don’t have to change the caller side if pass the whole object to the function.

interface can be replaced with type

When you use an interface as arguments definition, you can replace it with Type.

type MyType = {
    name: string;
    age: number;
}
function funcWithType(args: MyInterface) {
    return `I'm ${args.name}. I'm ${args.age} years old.`;
}

Related Articles

If you have wanted to refactor your code to change the parameter list to an object but you have once given up to do it because of various reasons, check the following article. By using Parameters utility type, you could refactor the code.

Comments

Copied title and URL