Using TypeScript Interfaces for Better Code Design: A Practical Guide to Implementation

Interfaces in TypeScript are a way to define contracts for variables, objects, and functions. They specify what properties and methods an object should have, without specifying the implementation details.

Here’s an example of how you can define an interface in TypeScript:


  
    /**
      Defining Interface 
     */
     
  interface Person {
    name: string;
    age: number;
    sayHello(): void;
  }

We can then use this interface to create objects that must adhere to the specified properties and methods:


     const person: Person = {
      name: "John Doe",
      age: 30,
      sayHello: () => console.log(`Hello, my name is ${person.name}`)
    };
    
    person.sayHello(); // Output: Hello, my name is John Doe

Interfaces for Class in Typescript

Interfaces in TypeScript can also be used with class types, allowing us to enforce a specific structure for classes:


  interface User {
    username: string;
    password: string;
  }

  class Employee implements User {
    constructor(public username: string, public password: string) {}
  }
  const user = new Employee("johndoe", "password123");
  console.log(user);
    
   
     //OutPut: Employee: {"username": "johndoe", "password": "password123"} 
  

Interfaces provide a way to define reusable contracts that can be implemented by multiple types, making it easier to ensure that objects in your code are properly structured and adhere to a common set of rules.

Duck Typing in Typescript

Duck typing is a concept in dynamic programming languages, including TypeScript, where the type of an object is determined by its structure and behavior, rather than its class or interface.

In TypeScript, you can use the “typeof” operator to determine the type of an object at runtime, and to check if it has the required properties and methods.

For example:


  let someObject = { name: "John Doe", age: 30 };

  if (typeof someObject === "object" && "name" in someObject && "age" in someObject) {
    console.log(`The object has a name property: ${someObject.name}`);
  }
  
   //OutPut: The object has a name property: John Doe 

This allows us to write code that is more flexible and can work with objects of different types, as long as they have the required properties and methods.

Note that while duck typing is a useful concept in dynamic programming languages, it can also make it more difficult to catch type-related errors at compile time.

To balance the benefits and drawbacks of duck typing, TypeScript provides static typing and type inference, which can catch type-related errors before your code runs, while still allowing you to take advantage of the benefits of duck typing when necessary.

Interfaces for function types in Typescript

In TypeScript, you can use interfaces to define the structure of functions. This allows you to specify the number of parameters, their types, and the return type of a function.

Here’s an example of how you can define an interface for a function type:


  interface AddFunction {
    (a: number, b: number): number;
  }
  
  const add: AddFunction = (a: number, b: number) => a + b;
  
  console.log(add(1, 2));  // Output: 3 
 

We can use this interface to ensure that any function assigned to the add variable must have the specified structure and behavior. This makes it easier to enforce a consistent contract for functions in our code, and can help catch errors before our code runs.

Function interfaces can also be used in conjunction with other types, such as classes and objects, to specify the expected behavior of functions that are used as properties or methods of these types.

For example, we can use a function interface to specify the type of a callback function that is passed as an argument to another function:


  interface AddFunction {
    (a: number, b: number): number;
  }
  
  const add: AddFunction = (a: number, b: number) => a + b;
  
  console.log(add(1, 2));  // Output: 3 
 

Function interfaces in TypeScript provide a powerful way to enforce a consistent contract for functions, making it easier to write maintainable and robust code.

Extending Interfaces in Typescript

In TypeScript, you can extend an interface by using the “extends” keyword. This allows you to inherit the members of an existing interface into a new interface.

Here’s an example:


  interface Person {
    name: string;
    age: number;
  }
  
  interface Developer extends Person {
    skills: string[];
  }
  
  const developer: Developer = {
    name: "John Doe",
    age: 30,
    skills: ["JavaScript", "TypeScript", "React"]
  };
  
  OutPut:// 
    {
    "name": "John Doe",
    "age": 30,
    "skills": [
      "JavaScript",
      "TypeScript",
      "React"
    ]
  }  
 

In this example, the “Developer” interface extends the “Person” interface and adds an additional “skills” property. A value of the “Developer” type must have all the properties of both “Person” and “Developer”.

Functions in Typescript

Introduction

Here we are going to describe about Functions in Typescript and better way to understand concept after making comparison with JavaScript Function.

In TypeScript, functions are similar to those in JavaScript with a some additional features. TypeScript adds type annotations to functions to specify the types of input arguments and the return type. This provides type checking at compile time and can help us to prevent runtime errors.

For example:


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

Another feature of TypeScript functions is the use of optional and default parameters.

In TypeScript, we can specify optional parameters by adding a ? after the parameter name, and default parameters can be specified by adding an = followed by the default value.

In JavaScript, functions are defined using the function keyword, followed by the function name, a list of parameters in parentheses, and the function body in curly braces.

For example:


    
    /**
      sample code
     */
     
     function add(a, b) {
      return a + b;
    }
    

JavaScript and Typescript both also supports arrow functions (also known as “fat arrow” functions), which are a shorthand for defining anonymous functions.

For example:


     const add = (a, b) => a + b; // Javascript
     const add = (a:number,b:number):number => a+b; //Typescript

Both TypeScript and JavaScript support function overloading, where you can define multiple functions with the same name but different parameter lists.

However, in JavaScript, the implementation must check the number and types of arguments passed in at runtime and respond accordingly.

In TypeScript, the type checker will pick the correct implementation based on the types of the arguments passed in at compile time. Further We will discuss in more details about function overloading .

Arrow functions in Typescript

In TypeScript, arrow functions (also known as “fat arrow” functions) are a shorthand for defining anonymous functions. They have a more concise syntax compared to regular functions and also automatically bind the this value of the function to the value of the surrounding context.

Here is an example of an arrow function in TypeScript:


  const addTwoNumbers = (num1: number, num2: number): number => num1 + num2;

In this example, the arrow function addTwoNumbers takes two parameters num1 and num2 of type number, and returns the sum of num1 and num2 as a number.

Note: that if the arrow function only has a single expression, the return type can be inferred and the return keyword can be omitted:


  const add = (a: number, b: number) => a + b;

Arrow functions are particularly useful when working with callback functions and when defining class methods. They can also be used as a more concise alternative to regular functions.

Function type in Typescript

In TypeScript, function types describe the signature of a function, including the parameters and return type.

In TypeScript, there are several ways that can be used to declare a function, including:

# Function type:

This type can be used to describe the signature of a function, including the parameters and return type. For example:


  let add: (a: number, b: number) => number;

# Callback function type:

This type is used to describe a function that is passed as an argument to another function.

For example:


  function callBack(cb: (data: string) => void) {
    cb("Hello World");
    }

# Anonymous function type:

This type is used to describe an anonymous function, which is a function that is declared without a name.

For example:


  let greet: (name: string) => void = function(name) {
    console.log("Hello, " + name);
  };

# Arrow function type:

This type is used to describe an arrow function, which is a short form of anonymous function.

For example:


  let greet: (name: string) => void = (name) => {
    console.log("Hello, " + name);
  };

Declaring Parameters in Typescript

In TypeScript, there are several ways to declare function parameters:

  • Required parameters: These are regular parameters that must be passed in when calling the function. They are declared by including the parameter name followed by its type in the function signature.
  • Optional parameters: These parameters can be omitted when calling the function. They are declared by adding a ? after the parameter name in the function signature.
  • Default parameters: These parameters have a default value that will be used if no value is passed in when calling the function. They are declared by using an = followed by the default value in the function signature.
  • Rest parameters: These allow you to represent an indefinite number of arguments as an array. They are declared using the syntax …parameterName: type[].
  • Object destructuring parameters: These allow you to extract values from objects and assign them to separate variables. They are declared using object destructuring syntax in the function signature.
  • Array destructuring parameters: These allow you to extract values from arrays and assign them to separate variables. They are declared using array destructuring syntax in the function signature.
  • Tuple parameters: These allow you to pass in a fixed number of arguments of different types. They are declared using a tuple type in the function signature.

In general, the combination of these different parameter types allows you to create flexible and reusable functions that can handle a wide range of inputs.

Although all ways for declaring parameters are important but above 4 ones are most important and commonly used in day to day life during writing code. So I like to discuss about these topics in more detail.

Optional and Default Parameters in Typescript

In TypeScript, optional parameters are defined by adding a “?” to the parameter name in a function declaration. These parameters can be omitted when calling the function, in which case their value will be undefined. Default parameters, on the other hand, allow you to set a default value for a parameter if no value is provided. This is done by using an “=” followed by the default value in the function declaration.

Here’s an example of using optional and default parameters in TypeScript:


  function greet(name: string, greeting?: string, message: string = "Good morning!") {
    if (greeting) {
      console.log(greeting + ", " + name + ". " + message);
    } else {
      console.log("Hello, " + name + ". " + message);
    }
  }
  
  greet("John");
  // Output: Hello, John. Good morning!
  
  greet("John", "Hi");
  // Output: Hi, John. Good morning!
  
  greet("John", "Hi", "How are you?");
  // Output: Hi, John. How are you?

In this example, the greetingparameter is optional, while the messageparameter has a default value of “Good morning!”. When calling the function, you can provide a value for both, either one, or neither.

Rest Parameters in Typescript

In TypeScript, rest parameters allow you to represent an indefinite number of arguments as an array. This is useful when you don’t know in advance how many arguments will be passed to a function.

The syntax for rest parameters is to use three dots (...) followed by the parameter name,

For example:


  function sum(...numbers: number[]) {
    let result = 0;
    for (const number of numbers) {
      result += number;
    }
    return result;
  }
  
  console.log(sum(2, 2, 3, 4, 4));
  
  // Output: 15

In this example, the sum function takes any number of arguments, collects them in an array numbers, and then adds them all up to find the sum.

Note that rest parameters must be the last parameter in a function signature. Additionally, you can only have one rest parameter per function.

Function Overloading in Typescript

In TypeScript, function overloading allows you to define multiple function signatures with the same name, but different parameters. The correct function to be called is determined based on the arguments passed in when the function is called.

Here’s an example of function overloading in TypeScript:


function combibne(a: number, b: number): number;
function combibne(a: string, b: string): string;
function combibne(a: any, b: any): any {
  return a + b;
}

const result1 = combibne(1, 2);
console.log(result1);
// Output: 3

const result2 = combibne("Hello, ", "world");
console.log(result2);
// Output: Hello, world

In this example, the combine function is overloaded to have two different signatures: one for adding numbers, and one for concatenating strings. The correct function is determined based on the types of the arguments passed in when the function is called.

Function overloading can be useful for creating functions that can handle a variety of inputs, but it’s important to note that JavaScript itself does not support function overloading.

The function overloading syntax in TypeScript is simply a way of providing type information for the function and is used to generate JavaScript code that implements the desired behavior.