Master Generics in TypeScript
Tags: typescript, programming, node, coding
Published: 2023-07-28 11:30:53


## Introduction

Generics are a way to create reusable components. They allow us to create a component that can work over a variety of types rather than a single one. This allows users to consume these components and use their own types. Generics are a way to create reusable components. They allow us to create a component that can work over a variety of types rather than a single one. This allows users to consume these components and use their own types.


### Defining Generic Functions:

To declare a generic function, you need to use angle brackets (<>) to specify one or more type parameters. These type parameters act as placeholders for the actual data types that will be used when calling the function.

- Here's the syntax for defining a generic function:

```typescript
function identity<T>(arg: T): T {
    return arg;
}
```
In above example, we have a function identity which returns whatever is passed to it. This function is generic because it works over a range of types. 

- We can call this function in two ways. First, we can pass all of the arguments, including the type argument, to the function:

```typescript

identity<string>("myString");  // type of output will be 'string'
```

- We can als use number type instead of string type:

```typescript

identity<number>(123);  // type of output will be 'number'
```

- Generic type can be inferred by the compiler which means we can omit the type argument:

```typescript

identity("myString");  // type of output will be 'string'
```

So now you can see with the power of generics, we can create a component that can work over a variety of types rather than a single one.



Now  we will see how to use generics in `functions`, `classes` and  `interfaces`, `constraints`.



Let us start with `functions`.

## Generic Functions 

Generic functions are functions that can work with a range of types. This allows us to reuse the components we build and can create a chain of functions that can work with multiple types.

Let us take an example of a function that takes an array of any type and returns the array with the same type.

```typescript

// Generic function
function genericFunctions<T>(arg: T[]): T[] {
  console.log(arg.length);  // Array has a .length, so no more error
  return arg;
```

In above example, we have a function genericFunctions which takes an array of any type and returns the array with the same type. We can call the function and pass an array of numbers or strings.

- **Passing an array of numbers:**

```typescript
// Passing an array of numbers
let outputFuncNumbers = genericFunctions<number>([1, 2, 3, 4, 5]);
console.log(outputFuncNumbers); // [1, 2, 3, 4, 5]

```

- **Passing an array of strings:**

```typescript
// Passing an array of strings

let outputFuncString = genericFunctions<string>(["Apple", "Orange", "Banana"]);
console.log( outputFuncString); // ["Apple", "Orange", "Banana"]

```
We can also omit the type argument and the compiler will infer the type for us:
 - **Inferred type of String:**
```typescript
// Inferred type of String
let outputFuncInfer = genericFunctions(["Apple", "Orange", "Banana"]);
console.log(outputFuncInfer); // ["Apple", "Orange", "Banana"]

```
I hope now you have a good understanding of generic functions. Now we will see how to use generics in `classes`.

## Generic Classes
To declare a generic class, you use the same angle brackets (<>) syntax as with generic functions. You define the type parameter inside the class declaration and then use it throughout the class as needed.

Here's the syntax for defining a generic class:

```typescript
class ClassName<T> {
  // Class properties and methods can use the type parameter 'T'
}
```

Let us take an example of a generic `class` that takes a type parameter T and has a property of type T and a method that returns a value of type T.

```typescript

class GenericClass<T> {

    // INSTANCE VARIABLE OF TYPE T
  private genericProperty: T;

// a constructor that takes an argument of type T
  constructor(value: T) {
    this.genericProperty = value;
  }

// a method that returns a value of type T
  public getGenericProperty(): T {
    return this.genericProperty;
  }
}

```

- **Creating an instance of GenericClass with number and String type argument:**

```typescript

// Creating an instance of GenericClass
let instance1 = new GenericClass<number>(123);
console.log(instance1.getGenericProperty()); // 123 


// create instance of generic class with string type argument

let instance2 = new GenericClass<string>("Hello, world");
console.log(instance2.getGenericProperty());

```
As you can see in above example, we have created an instance of GenericClass with number and String type argument. This is Mean you can create an instance of GenericClass with any type argument. 

If your new to OOP in typescript, you can read my article on[Object Oriented Programming with Typescript](https://dev.to/cliff123tech/oop-typescript-jk4)



So now you have a good understanding of generic classes. Now we will see how to use generics in `interfaces`.


## Generic Interfaces   
Generic interfaces are interfaces that can work with a range of types. This allows us to reuse the components we build and can create a chain of interfaces that can work with multiple types.Similar to generic functions and classes, you can create generic interfaces that work with different types. This allows you to define flexible interfaces that can adapt to various data structures while providing type safety.


Here's the syntax for defining a generic interface

```typescript

// syntax for defining a generic interface:

interface InterfaceName<T> {
  // Interface properties and methods can use the type parameter 'T'
}

```


Now let us take an example of a generic interface that takes a type parameter T and has a property of type T and a method that returns a value of type T.

```typescript


interface Box<T> {
  value: T;
}

const box1: Box<number> = { value: 42 };
const box2: Box<string> = { value: "Hello" };

console.log(box1, box2); // { value: 42 } { value: 'Hello' }


```

Now let see another example Generic type with multiple arguments


```typescript
function genericFunction<T, U>(x: T, y: U): void {
  console.log(x, y);
}

genericFunction<number, string>(1, "Hello"); // 1 "Hello"

``` 

### Generic Functions with Interfaces:

You can also use generic functions with interfaces to define contracts for functions that can work with various data types.

```typescript
interface MathOperation<T> {
  perform: (a: T, b: T) => T;
}

const add: MathOperation<number> = {
  perform: (a, b) => a + b,
};

const concatenate: MathOperation<string> = {
  perform: (a, b) => a + b,
};

console.log(add.perform(5, 10)); // Output: 15
console.log(concatenate.perform("Hello, ", "World!")); // Output: "Hello, World!"
``` 

Now with interface you can see we can create and interface and add generic type to it and we can use it for multiple types.

 So now you have a good understanding of generic interfaces. Now we will see how to use generics in `constraints`.


**Defining Constraints:**

To apply constraints, you use the `extends` keyword followed by the type or interface you want to use as the constraint. This tells TypeScript that the type parameter used in the generic code must be a subtype of the specified constraint.

Here's the syntax for defining constraints:

```typescript
function functionName<T extends ConstraintType>(param: T): void {
  // Function logic here
}
```


**Example:**

```typescript
interface Lengthy {
  length: number;
}

function printLength<T extends Lengthy>(arg: T): void {
  console.log("Length:", arg.length);
}

printLength("Hello"); // Output: Length: 5
printLength([1, 2, 3]); // Output: Length: 3
printLength({ length: 10 }); // Output: Length: 10

printLength(42); // Error: Type 'number' does not satisfy the constraint 'Lengthy'.
```

In this example, we define the `Lengthy` interface with a `length` property. The `printLength` function takes a generic type 'T', but it must satisfy the `Lengthy` constraint. Therefore, the function can be called with strings, arrays, and objects that have a `length` property, but it will produce a compilation error if you try to pass a type that doesn't meet the constraint (like `number`).

**Multiple Constraints:**

You can also apply multiple constraints to a generic type by using the `extends` keyword followed by an intersection (`&`) of multiple types or interfaces.

**Example:**

```typescript
interface Printable {
  print: () => void;
}

interface Serializable {
  serialize: () => string;
}

function process<T extends Printable & Serializable>(obj: T): void {
  obj.print();
  console.log("Serialized:", obj.serialize());
}


const myObject: Printable & Serializable = {
  print() {
    console.log("Printing...");
  },
  serialize() {
    return "Serialized data";
  },
};

process(myObject); // Output: Printing... Serialized: Serialized data
```

In this example, the `process` function takes a generic type 'T', which must satisfy both the `Printable` and `Serializable` constraints. This ensures that the function can be called only with objects that implement both interfaces. The `myObject` object satisfies both constraints, so it can be passed to the `process` function.

**Using Type Constraints with Classes:**

Constraints can also be applied to generic classes, ensuring that the class accepts only certain types that meet the constraint.

**Example:**

```typescript
interface Animal {
  name: string;
}

class Zoo<T extends Animal> {
  constructor(private animals: T[]) {}

  listAnimals(): void {
    this.animals.forEach(animal => {
      console.log("Name:", animal.name);
    });
  }
}

const zoo = new Zoo([
  { name: "Lion" },
  { name: "Elephant" },
  { name: "Giraffe" },
]);

zoo.listAnimals();
```

In this example, the `Zoo` class is a generic class that takes a type parameter 'T', which must satisfy the `Animal` constraint. This ensures that the array of animals passed to the constructor contains objects with the `name` property (which is defined in the `Animal` interface).

Constraints provide a powerful way to make generic code more robust and safe by specifying the types it can work with. They allow you to define clear contracts and ensure that the generic code operates only on suitable types, leading to more reliable and maintainable programs.


Thank you for getting the point of this article. I hope you enjoyed it. If you have any questions, please feel free to ask in the comments section below. I will try to answer them as soon as possible. You  can find all the source  code on my [Github](https://github.com/Clifftech123/Master-Generics-in-Typescript-blog)


let connect with me on [Twitter](https://twitter.com/cliff123tech) and [LinkedIn](https://www.linkedin.com/in/isaiah-clifford-opoku-a506a51b2/)