TypeScript Advanced Types — this Type and Dynamic Types
Tags: typescript, javascript, webdev, programming
Published: 2021-01-23 20:04:15

**Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62**

**Subscribe to my email list now at http://jauyeung.net/subscribe/**

TypeScript has many advanced type capabilities, which makes writing dynamically typed code easy. It also facilitates the adoption of existing JavaScript code since it lets us keep the dynamic capabilities of JavaScript while using the type-checking capability of TypeScript. There are many kinds of advanced types in TypeScript, like intersection types, union types, type guards, nullable types, and type aliases, and more. In this article, we’ll look at the `this` type and creating dynamic types with index signatures and mapped types.

This Type
=========

In TypeScript, we can use `this` as a type. It represents the subtype of the containing class or interface. We can use it to create fluent interfaces easily since we know that each method in the class will be returning the instance of a class.

For example, we can use it to define a class with chainable methods like in the following code:

```
class StringAdder {  
  value: string = '';  
  getValue(): string {  
    return this.value;  
  } 

  addFoo(): this {  
    this.value += 'foo';  
    return this;  
  } 

  addBar(): this {  
    this.value += 'bar';  
    return this;  
  } 

  addGreeting(name: string): this {  
    this.value += `Hi ${name}`;  
    return this;  
  }  
}
const stringAdder: StringAdder = new StringAdder();  
const str = stringAdder  
  .addFoo()  
  .addBar()  
  .addGreeting('Jane')  
  .getValue();  
console.log(str);
```

In the code above, the `addFoo`, `addBar`, and `addGreeting` methods all return the instance of the `StringAdder` class, which lets us chain more method calls of the instance to it once it’s instantiated. The chaining is made possible by the `this` return type that we have in each method.

Index Types
===========

To make the TypeScript compiler check code with dynamic property names, we can use index types. We can use the `extends keyof` keyword combination to denote that the type has the property names of another type. For example, we can write:

```
function choose<U, K extends keyof U>(o: U, propNames: K[]): U[K][] {  
  return propNames.map(n => o[n]);  
}
```

Then we can use the `choose` function as in the following code:

```
function choose<U, K extends keyof U>(o: U, propNames: K[]): U[K][] {  
  return propNames.map(n => o[n]);  
}

const obj = {  
  a: 1,  
  b: 2,  
  c: 3  
}  
choose(obj, ['a', 'b'])
```

Then we get the values:

```
[1, 2]
```

if we log the results of the `choose` function. If we pass in a property name that doesn’t exist in the `obj` object into the array in the second of the `choose` function, then we get an error from the TypeScript compiler. So if we write something like the following code:

```
function choose<U, K extends keyof U>(o: U, propNames: K[]): U[K][] {  
  return propNames.map(n => o[n]);  
}const obj = {  
  a: 1,  
  b: 2,  
  c: 3  
}  
const arr = choose(obj, ['d']);
```

Then we get the error:

```
Type 'string' is not assignable to type '"a" | "b" | "c"'.(2322)
```

In the examples above, `keyof U` is the same as the string literal type `“a” | “b” | “c”` since we passed in the type of the generic `U` type marker where the actual type is inferred from the object that we pass in into the first argument. The `K extends keyof U` part means that the second argument must have an array of some or all the key names of whatever is passed into the first argument, which we denoted by the generic `U` type. Then we defined the return type as an array of values that we get by looping through the object we pass into the first argument, hence we have the `U[K][]` type. `U[K][]` is also called the index access operator.



Index types and Index Signatures
================================

An index signature is a parameter that must be of type string or number in a TypeScript interface. We can use it to denote the properties of a dynamic object. For example, we can use it like we do in the following code:

```
interface DynamicObject<T> {  
  [key: string]: T;  
}  
let obj: DynamicObject<number> = {  
  foo: 1,  
  bar: 2  
};  
let key: keyof DynamicObject<number> = 'foo';  
let value: DynamicObject<number>['foo'] = obj[key];
```

In the code above, we defined a `DynamicObject<T>` interface which takes a dynamic type for its members. We have an index signature called the `key` which is a string. It can also be a number. The type of the dynamic members is denoted by `T`, which is a generic type marker. This means that we can pass in any data type into it.

Then we defined the `obj` object, which is of type `DyanmicObject<number>`. This makes use of the `DynamicObject` interface we created earlier. Then we defined the `key` variable, which has the type `keyof DynamicObject<number>`, which means that it has to be a string or a number. This means that the `key` variable must have one of the property names as the value. Then we defined the `value` variable, which must have the value of an object of type `DynamicObject` .

This means that we can’t assign anything other than a string or number to the `key` variable. So if write something like:

```
let key: keyof DynamicObject<number> = false;
```

Then we get the following error message from the TypeScript compiler:

```
Type 'false' is not assignable to type 'string | number'.(2322)
```

Mapped Types
============

We can create a new type by mapping the members of an existing type into the new type. This is called a mapped type.

We can create mapped types like we do in the following code:

```
interface Person {  
  name: string;  
  age: number;  
}

type ReadOnly<T> = {  
  readonly [P in keyof T]: T[P];  
}

type PartialType<T> = {  
  [P in keyof T]?: T[P];  
}

type ReadOnlyPerson = ReadOnly<Person>;  
type PartialPerson = PartialType<Person>;let readOnlyPerson: ReadOnlyPerson = {  
  name: 'Jane',  
  age: 20  
}
readOnlyPerson.name = 'Joe';  
readOnlyPerson.age = 20;
```

In the code above, we created the `ReadOnly` type alias to let us map the members of an existing type into a new type by setting each member of the type as `readonly`. This isn’t a new type on its own since we need to pass in a type to the generic type marker `T` . Then we create an alias for the types that we defined by passing in the `Person` type into the `ReadOnly` alias and `Partial` alias respectively.

Next we defined a `ReadOnlyPerson` object with the `name` and `age` properties set. Then when we try to set the values again, then we get the following errors:

```
Cannot assign to 'name' because it is a read-only property.(2540)Cannot assign to 'age' because it is a read-only property.(2540)
```

Which means that the `readonly` property from the `ReadOnly` type alias is being enforced. Likewise, we can do the same with the `PartialType` type alias. We have defined the `PartialPerson` type by mapping the members of the `Person` type to the `PartialPerson` type with the `PartialPerson` type. Then we can define a `PartialPerson` object like in the following code:

```
let partialPerson: PartialPerson = {};
```

As we can see, we can omit properties from the `partialPerson` object we as want.

We can add new members to the mapped type alias by creating an intersection type from it. Since we used the `type` keyword to define the mapped types, they’re actually actually types. They are actually type aliases. This means that we can’t put members straight inside, even though they look like interfaces.

To add members, we can write something like the following:

```
interface Person {  
  name: string;  
  age: number;  
}

type ReadOnly<T> = {  
  readonly [P in keyof T]: T[P];  
}

type ReadOnlyEmployee = ReadOnly<Person> & {  
  employeeCode: string;  
};

let readOnlyPerson: ReadOnlyEmployee = {  
  name: 'Jane',  
  age: 20,  
  employeeCode: '123'  
}
```

`Readonly<T>` and `Partial<T>` are included in the TypeScript standard library. `Readonly` maps the members of the type that we pass into the generic type placeholder into read-only members. The `Partial` keyword lets us map members of a type, into nullable members.

Conclusion
==========

In TypeScript, we can use `this` as a type. It represents the subtype of the containing class or interface. We can use it to create fluent interfaces easily since we know that each method in the class will be returning the instance of a class. An index signature is a parameter that must be of type string or number in a TypeScript interface.

We can use it to denote the properties of a dynamic object. To convert members of a type to add some attributes to them, we can map the members of an existing type into the new type to add the attributes to the interface with mapped types.