# Jumpstart on advance typescript Part 1



I started learning Typescript a couple of months back and initially, it was quite overwhelming for me because I used to write plain old javascript, where no strict type checking, no interfaces, no generic types. Seeing these feature/keywords in a large project was not recognized by my mental modals for initial few days at that moment just only one way I have left to do is to learn those jargons and make them my friend

In this blog, I will try to explain some of Typescript's advanced features which is I learned during this process and are very useful. The main idea is to explain it in the simplest way possible. I am also assuming you already have a basic understanding of how a strict type system works.


## ⚙️ Generics
A good piece of code is that which is reusable and flexible. Generics in Typescript provides a way to write components which can work over various kind of data type rather than a single one.

If we are performing some operation on more than one data types we don't need to repeat the same code block for each data type. Generics allow us to do so while keeping it type-safe at the same time.

Let's see an example where we are just returning what we have passed.

```ts
// Will return string
function rebound(arg: string): string {
    return arg
}

// Will return number
function rebound(arg: number): number {
    return arg
}
```
We can combine these two functions using `any` type

```ts
function rebound(arg: any): any {
    return arg
}
```
but as you already know `any` is not type safe, it means compiler can't catch error on compile time. 

Now let's make it type safe using `Generics`

```ts
function rebound<T>(args: T): T {
    return args
}
```
You must be thinking

<i>Wait the heck is `<T>` or `T` </i>

These are called `Type` parameters. This lets users use this function with any type. It tells typescript which data-type argument is passed. let see the example of how to use it. 

```ts
const message = rebound<string>('Hello, World'); // OK

const count = rebound<number>(200); // OK

const count = rebound<number>('200'); // Error, data-type expected is number but passed string
```

You can use multiple type parameters as well

```ts
function someFunction<T, U>(arg1: T, args2: U) { ... }
```

Not only `functions`, but `Classes` and `interfaces` can be `Generic` as well. let's see the below example where a generic interface is used as type

### Generic interface as Type 

```ts
interface KeyValuePair<T, U> {
    key: T;
    value: U;
}

let keyPair1: KeyValuePair<number, string> = { key:1, value:"Manish" }; // OK
let keyPair2: KeyValuePair<number, number> = { key:1, value:1988 }; // OK

```
As you can see in the above example, by using generic interface as type, we can specify the data type of key and value

### Generic interface as Function Type 
In same way, we can use interface as a function type.

```ts
interface KeyValueLogger<T, U>
{
    (key: T, val: U): void;
};

function logNumKeyPairs(key:number, value:number):void { 
    console.log('logNumKeyPairs: key = ' + key + ', value = ' + value)
}

function logStringKeyPairs(key: number, value:string):void { 
    console.log('logStringKeyPairs: key = '+ key + ', value = ' + value)
}

let numLogger: KeyValueLogger<number, number> = logNumKeyPairs;
numLogger(1, 12345); //Output: logNumKeyPairs: key = 1, value = 12345 

let strLogger: KeyValueLogger<number, string> = logStringKeyPairs;
strLogger(1, "kumar"); //Output: logStringKeyPairs: key = 1, value = Bill 
```

### Generic classes
So we learned about generic functions and interfaces. Similar way we can create generic classes too. Let's see below example

```ts
interface KeyValuePair<T, U> {
    key: T;
    value: U;
}

class Dictionary<T, U> {

    private keyValueCollection: Array<keyValuePair<T, U>>

    add(key:T, value: U) {
        if(this.keyValueCollection.findIndex(pair =>  pair.key === key) > -1) {
            throw new Error(key + ' key already exist in dictionary');
        }
        this.keyValueCollection.push({key, value});
    }

    remove(key: T) {
        const index = this.keyValueCollection.findIndex(pair =>  pair.key === key);

        if(index > -1) {
            this.keyValueCollection.splice(index, 1);
        } else {
            throw new Error(key + ' key does not exist in dictionary');
        }
    }
}

const dictionary = new Dictionary<string, string>();
dictionary.add('name', 'john');
dictionary.add('country', 'India');

```
You can see in the above example, I have used both generic interface and generic class, which gives us flexibility to create a `HashMap` with custom implementation.


## ⛓Conditional Type

Conditional type is something which we are not going to use very frequently directly but we use them indirectly a lot of time. In very simple terms it is a kind of ternary expression for Types.
Let's look at a very basic example

```ts
type BookPages = bookId extends string[] ? number[] : number;
```
in above example, we are first check if `bookId` variable is a array of string, if yes then we will make `BookPages` array of number otherwise the only number

according to [Typescript definition of conditional types](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html)

*"A conditional type selects one of two possible types based on a condition expressed as a type relationship test"*

### Filtering using conditional type

As we already seen a basic example lets see some more examples to get a better understanding. Let's create a filter type which will select a subset of a union type

```ts
type Filter<T, U> = T extends U ? never : T;

type CarTypes = 'SUV' | 'HATCHBACK' | 'SEDAN' | 'MUSCLE' | 'VINTAGE';

type NonVintageCarTypes = Filter<CarTypes, 'VINTAGE'>; // 'SUV' | 'HATCHBACK' | 'SEDAN' | 'MUSCLE'
```
The conditional type here is just going to check each string literal in string literal union parameter (*T in this case*), is assignable to a string literal union under the second type parameter (*U in this case*). If yes, `never` is returned which means no result otherwise string literal is added to resulting union

In the above example, we filtered out one of union type from `CarType` and created a new type `NonVintageCarTypes`. 

Basically this is how is in-built `Exclude` type is defined. So at this point, we can create `Include` type very easily, we just need to reverse the condition
```ts
type Include<T, U> = T extends U ? T : never;
```
Now we get some understanding about conditional type, hence it won't be very tough to create a copy of built-in `NonNullable` type using conditional type 

```ts
type MyNonNullable<T> = T extends null ? never : T;

type SomeNullableType = string | number | null;

type SomeNonNullableType = MyNonNullable<SomeNullableType>;
```

### Replace overloading with conditional types

Suppose there is an entertainment center which maintains a database of Books and TV series as below

```ts
interface Book {
  id: string;
  name: string;
  tableOfContents: string[];
}

interface TvSeries {
  id: number;
  name: string;
  Episode: number;
}

interface IItemService {
  getItem<T>(id: T): Book | Tv;
}

let itemService: IItemService;
```
In `IItemService` interface, `getItem` function can return detail of `Book` or `TvSeries`. If you look closely we have id as `string` then we need to return `Book` and if it is `number` we need to return `TvSeries`. We could achieve this with overloading as below and it works perfectly

```ts
interface IItemService {
  getItem(id: string): Book;
  getItem(id: number): Tv;
  getItem<T>(id: T): Book | Tv;
}
```
But using conditional types we can keep only one definition. Let's see below change 

```ts
interface IItemService {
  getItem<T>(id: T): T extends string ? Book : Tv;
}
```
Isn't it very handy!

There are many ways we can use conditional type in conjunction with other Typescript fundamentals. I will try to cover some of those in my next blog. Thanks for reading it
