Categories
JavaScript

How To Use LocalStorage to Store Data in the Browser

Storing data in a browser is something we do often for web apps. Often, we need to store some temporary data that’s user-specific on the browser. We can do this with the browser’s local-storage capabilities.

With local storage, we store data on the browser as strings. It’s a key value–based storage medium, allowing us to get data by keys and also setting values with the key as the identifier. All data is stored as strings. If a piece of data is not a string, then it’ll be converted to a string before being stored.

Once they’re saved, they’re there as long as we don’t delete the data. We can remove data by its key or remove them all at the same time. Every app that’s hosted on the same domain can access the data, so we can host multiple web apps under the same domain and still get the same data in all the apps.

This means we can easily modularize apps into smaller apps, and we won’t have a problem with browser data access as long as all the apps are hosted in the same domain.

Unlike cookies, local-storage data has no expiration time, so it’ll still be there if we don’t do anything to delete it.

We access the browser’s local storage with the localStorage object. It’ll thrown a SecurityError if we don’t access localStorage with the http protocol. This means that any URL starting with protocols like file: or data: will fail with this error.


Saving Data

We can use the setItem method to save data to a browser’s local storage. It takes two arguments. The first argument is a string with the key of the data, and the second argument is a string with the value of the corresponding key we passed into the first argument.

It throws an exception if the storage is full. Safari has the storage quota set to zero bytes in private mode, but it allows storage in private mode using separate data containers. This means we should catch exceptions from setItem.

For example, we can write:

localStorage.setItem('foo', 'bar');

Then once we run that, we’ll see the entry in the Application tab, under the Local Storage section of Chrome.

We can also write …

localStorage.foo = 'bar';

… to save data.

Bracket notation also works for assigning values to local storage. For example, we can write …

localStorage['foo'] = 'bar';

… to set the local storage item with the key 'foo' and the value 'bar' .

However, we shouldn’t use the dot or bracket notation instead of setItem. We don’t want to accidentally override things like the setItem method by setting a string to it, like trying to save data by using setItem as the key.

Saving object data

If we try to save objects, we’ll get [object Object] as the value. To actually save the content, we have to use the JSON.stringify method. For example, instead of writing …

localStorage.setItem('foo', {foo: 'bar'});

… we write:

localStorage.setItem('foo', JSON.stringify({foo: 'bar'}));

Then, we get {“foo”:”bar”} as the value for the entry with the foo key.

Repeated calling

If we call the setItem method repeated with the same key, then the existing value with the key is overwritten. For example, if we write …

localStorage.setItem('foo', 1);  
localStorage.setItem('foo', 2);

… then we get 2 as the value for the entry with the key foo since it’s the last value that was saved.


Getting Data

We can get data with the getItem method or use the dot notation to get data like any other object. getItem takes one argument — a string for the key in which we want to get a value with. It returns a string with the value if it’s set for the given key or null if it’s not.

For example, we can write:

localStorage.getItem('foo');

Then, we get the value 'bar' if we run the setItem code from the previous section.

We can also write:

localStorage.foo;

Alternatively, we can write:

localStorage['foo'];

The bracket notation is handy for accessing values with corresponding keys that aren’t valid object-property names.

There’s also a key method to get the name of the key in the local storage given the position number. It takes one argument, which is an integer that’s zero or higher. The first position is numbered 0. For example, if we have the following code…

localStorage.key(1)

… then we get the key name of the second entry in the local storage.


Removing a Single Entry in Local Storage

We can remove a single entry from the local storage given the key with the removeItem method. It takes one argument, which is a string with the key name of the entry.

For example, if we want to remove the entry with the key 'foo', then we can write:

loocalStorage.removeItem('foo');

The code above will remove the local-storage entry with the key name 'foo'.

Alternatively, we can use the delete operator to remove the item given the key name. For example, we can remove the local-storage entry with the key 'foo' by running:

delete localStorage.foo;

We can also use the bracket notation to do the same thing, so we can write …

delete localStorage['foo'];

… to remove the same entry.

However, we shouldn’t use the dot or bracket notation instead of setItem. We don’t want to accidentally override things like the setItem method by setting a string to it, like trying to save data by using setItem as the key.


Clearing Local Storage

We can use the clear() method to clear all entries in local storage.

We can write …

localStorage.clear()

… to clear all entries. We shouldn’t see anything in the browser’s local-storage section in the Application tab once we run this method.


Browser Compatibility

Local storage is available for almost all modern browsers, so it’s safe to use pretty much anywhere. Even Internet Explorer has had support for it since version 8, so it’s not a new technology. It’s much better than browser-side cookies for persistent data since it doesn’t expire and there are methods to manipulate the data.

With local storage on the browser, storing data client-side is easier than ever. We can do a lot by just calling the methods we outlined above. Whatever is saved is going to be saved as strings. If the data passed into the second argument of the setItem isn’t a string, then it’ll automatically be converted to a string.

Categories
JavaScript TypeScript

TypeScript Advanced Types — this Type and Dynamic Types

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.

Categories
GraphQL JavaScript Nodejs

An Introduction to GraphQL

GraphQL is a query language for our API and a server-side runtime for running queries by using a type system for our data.

In this article, we’ll look at how to make basic queries to a GraphQL API.

Defining the API

We define an API by defining the types and fields for those types and provide functions for each field on each type.

For example, if we have the following type:

type Query {  
  person: Person  
}

Then we have to create a function for the corresponding type to return the data:

function Query_person(request) {  
  return request.person;  
}

Making Queries

Once we have a GraphQL service running, we can send GraphQL queries to validate and execute on the server.

For example, we can make a query as follows:

{  
  person {  
    firstName  
  }  
}

Then we may get JSON like the following:

{  
  "person": {  
    "firstName": "Joe"  
  }  
}

Queries and Mutations

Queries are for getting data from the GraphQL server and mutations are used for manipulating data stored on the server.

For example, the following is a query to get a person’s name:

{  
  person {  
    name  
  }  
}

Then we may get the following JSON from the server:

{  
  "data": {  
    "person": {  
      "name": "Joe"  
    }  
  }  
}

The field name returns a String type.

We can change the query as we wish if we want to get more data. For example, if we write the following query:

{  
  person {  
    name  
    friends {  
      name  
    }  
  }  
}

Then we may get something like the following as a response:

{  
  "data": {  
    "person": {  
      "name": "Joe",  
      "friends": [  
        {  
          "name": "Jane"  
        },  
        {  
          "name": "John"  
        }  
      ]  
    }  
  }  
}

The example above has friends being an array. They look the same from the query perspectively, but the server knows what to return based on the type specified.

Arguments

We can pass in arguments to queries and mutations. We can do a lot more with queries if we pass in arguments to it.

For example, we can pass in an argument as follows:

{  
  person(id: "1000") {  
    name      
  }  
}

Then we get something like:

{  
  "data": {  
    "person": {  
      "name": "Luke"  
    }  
  }  
}

from the server.

With GraphQL, we can pass in arguments to nested objects. For example, we can write:

{  
  person(id: "1000") {  
    name  
    height(unit: METER)  
  }  
}

Then we may get the following response:

{  
  "data": {  
    "person": {  
      "name": "Luke",  
      "height": 1.9  
    }  
  }  
}

In the example, the height field has a unit which is an enum type that represents a finite set of values.

unit may either be METER or FOOT.

Fragments

We can define fragments to let us reuse complex queries.

For example, we can define a fragment and use it as follows:

{  
  leftComparison: person(episode: FOO) {  
    ...comparisonFields  
  }  
  rightComparison: person(episode: BAR) {  
    ...comparisonFields  
  }  
}  
​  
fragment comparisonFields on Character {  
  name  
  appearsIn  
  friends {  
    name  
  }  
}

In the code above, we defined the comparisonFields fragment which has the list of fields we want to include in each query.

Then we have the leftComparison and rightComparison queries which include the fields of the comparisonFields fragment by using the ... operator.

Then we get something like:

{  
  "data": {  
    "leftComparison": {  
      "name": "Luke",  
      "appearsIn": [  
        "FOO",  
        "BAR"  
      ],  
      "friends": [  
        {  
          "name": "Jane"  
        },  
        {  
          "name": "John"  
        }  
      ]  
    },  
    "rightComparison": {  
      "name": "Mary",  
      "appearsIn": [  
        "FOO",  
        "BAR"  
      ],  
      "friends": [  
        {  
          "name": "Mary"  
        },  
        {  
          "name": "Alex"  
        }  
      ]  
    }  
  }  
}

Using variables inside fragments

We can pass in variables into fragments as follows:

query PersonComparison($first: Int = 3){  
  leftComparison: person(episode: FOO) {  
    ...comparisonFields  
  }  
  rightComparison: person(episode: BAR) {  
    ...comparisonFields  
  }  
}  
​  
fragment comparisonFields on Character {  
  name  
  appearsIn  
  friends(first: $first) {  
    name  
  }  
}

Then we may get something like:

{  
  "data": {  
    "leftComparison": {  
      "name": "Luke",  
      "appearsIn": [  
        "FOO",  
        "BAR"  
      ],  
      "friends": [  
        {  
          "name": "Jane"  
        },  
        {  
          "name": "John"  
        }  
      ]  
    },  
    "rightComparison": {  
      "name": "Mary",  
      "appearsIn": [  
        "FOO",  
        "BAR"  
      ],  
      "friends": [  
        {  
          "name": "Mary"  
        },  
        {  
          "name": "Alex"  
        }  
      ]  
    }  
  }  
}

as a response.

The operation type may either be a query, mutation, or subscription and describes what operator we’re intending to do. It’s required unless we’re using the query shorthand syntax. In that case, we can’t supply a name or variable definition for our operation.

The operation name is a meaningful and explicit name for our operation. It’s required in multi-operation documents. But its use is encouraged because it’s helpful for debugging and server-side logging.

It’s easy to identify the operation with a name.

Conclusion

GraphQL is a query language that lets us send requests to a server in a clear way. It works by sending nested objects with operation type and name along with any variables to the server.

Then the server will return us the response that we’re looking for.

Operation types include queries for getting data and mutations for making changes to data on the server.

Categories
JavaScript TypeScript

TypeScript Advanced Types — Nullable Types and Type Aliases

TypeScript has many advanced type capabilities and which make 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 multiple kinds of advanced types in TypeScript, like intersection types, union types, type guards, nullable types, and type aliases, and more. In this article, we look at nullable types and type aliases.

Nullable Types

To let us assign undefined to a property with the --strictNullChecks flag on, TypeScript supports nullable types. With the flag on, we can’t assign undefined to type members that don’t have the nullable operator attached to it. To use it, we just put a question mark after the member name for the type we want to use.

If we have the strictNullChecks flag on and we set a value of a property to null or undefined , then like we do in the following code:

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

let p: Person = {  
  name: 'Jane',  
  age: null  
}

Then we get the following errors:

Type 'null' is not assignable to type 'number'.(2322)input.ts(3, 3): The expected type comes from property 'age' which is declared here on type 'Person'

The errors above won’t appear if we have strictNullChecks off and the TypeScript compiler will allow the code to be compiled.

If we have the strictNullChecks flag on and we want to be able to set undefined to a property as the value, then we can make the property nullable. For example, we can set a member of an interface to be nullable with the following code:

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

let p: Person = {  
  name: 'Jane',  
  age: undefined  
}

In the code above, we added a question mark after the age member in the Person interface to make it nullable. Then when we define the object, we can set age to undefined. We can’t still set age to null . If we try to do that, we get:

Type 'null' is not assignable to type 'number | undefined'.(2322)input.ts(3, 3): The expected type comes from property 'age' which is declared here on type 'Person'

As we can see, a nullable type is just a union type between the type that we declared and the undefined type. This also means that we can use type guards with it like any other union type. For example, if we want to only get the age if it’s defined, we can write the following code:

const getAge = (age?: number) => {  
  if (age === undefined) {  
    return 0  
  }  
  else {  
    return age.toString();  
  }  
}

In the getAge function, we first check if the age parameter is undefined . If it is, then we return 0. Otherwise, we can call the toString() method on it, which is available to number objects.

Likewise, we can eliminate null values with a similar kind of code, for instance, we can write:

const getAge = (age?: number | null) => {  
  if (age === null) {  
    return 0  
  }    
  else if (age === undefined) {  
    return 0  
  }  
  else {  
    return age.toString();  
  }  
}

This comes in handy because nullable types exclude null from being assigned with strictNullChecks on, so if we want null to be able to be passed in as a value for the age parameter, then we need to add null to the union type. We can also combine the first 2 if blocks into one:

const getAge = (age?: number | null) => {  
  if (age === null || age === undefined) {  
    return 0  
  }  
  else {  
    return age.toString();  
  }  
}

Type Aliases

If we want to create a new name for an existing type, we can add a type alias to the type. This can be used for many types, including primitives, unions, tuples, and any other type that we can write by hand. To create a type alias, we can use the type keyword to do so. For example, if we want to add an alias to a union type, we can write the following code:

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

interface Employee {  
  employeeCode: number;  
}

type Laborer = Person & Employee;  
let laborer: Laborer = {  
  name: 'Joe',  
  age: 20,  
  employeeCode: 100  
}

The declaration of laborer is the same as using the intersection type directly to type the laborer object, as we do below:

let laborer: Person & Employee = {  
  name: 'Joe',  
  age: 20,  
  employeeCode: 100  
}

We can declare type alias for primitive types like we any other kinds of types. For example, we can make a union type with different primitive types as we do in the following code:

type PossiblyNumber = number | string | null | undefined;  
let x: PossiblyNumber = 2;  
let y: PossiblyNumber = '2';  
let a: PossiblyNumber = null;  
let b: PossiblyNumber = undefined;

In the code above, the PossiblyNumber type can be a number, string, null or undefined . If we try to assign an invalid to it like a boolean as in the following code:

let c: PossiblyNumber = false;

We get the following error:

Type 'false' is not assignable to type 'PossiblyNumber'.(2322)

just like any other invalid value assignment.

We can also include generic type markers in our type aliases. For example, we can write:

type Foo<T> = { value: T };

Generic type aliases can also be referenced in the properties of a type. For example, we can write:

type Tree<T> = {  
  value: T;  
  left: Tree<T>;  
  center: Tree<T>;  
  right: Tree<T>;  
}

Then we can use the Tree type as we do in the following code:

type Tree<T> = {    
  value: T,  
  left: Tree<T>;  
  center: Tree<T>;  
  right: Tree<T>;  
}

let tree: Tree<string> = {} as Tree<string>;  
tree.value = 'Jane';tree.left = {} as Tree<string>  
tree.left.value = 'Joe';  
tree.left.left = {} as Tree<string>;  
tree.left.left.value = 'Amy';  
tree.left.right = {} as Tree<string>  
tree.left.right.value = 'James';tree.center = {} as Tree<string>  
tree.center.value = 'Joe';tree.right = {} as Tree<string>  
tree.right.value = 'Joe';console.log(tree);

The console.log for tree on the last line should get us:

{  
  "value": "Jane",  
  "left": {  
    "value": "Joe",  
    "left": {  
      "value": "Amy"  
    },  
    "right": {  
      "value": "James"  
    }  
  },  
  "center": {  
    "value": "Joe"  
  },  
  "right": {  
    "value": "Joe"  
  }  
}

Nullable types are useful is we want to be able to assign undefined to a property when strictNullChecks flag is on when in our TypeScript compiler configuration. It’s simply a union type between whatever type you have and undefined . It’s denoted by a question mark after the property name. This means we can use type guards with it like any other union type. Note that nullable types don’t allow null values to be assigned to it since nullable types are only needed when strictNullChecks flag is on. Type alias let us create a new name for types that we already have. We can also use generics with type alias, but we can’t use them as standalone types.

Categories
JavaScript TypeScript

TypeScript Advanced Types — Conditional Types

TypeScript has many advanced type capabilities and 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 multiple 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 conditional types.

Conditional Types

Since TypeScript 2.8, we can define types with conditional tests. This lets us add types to data that can have different types according to the condition we set. The general expression for defining a conditional type in TypeScript is the following:

T extends U ? X : Y

T extends U describes the relationship between the generic types T and U . If T extends U is true then the X type is expected. Otherwise, the Y type is expected. For example, we can use it as in the following code:

interface Animal {    
  kind: string;  
}

interface Cat extends Animal {  
  name: string;  
}

interface Dog {  
  name: string;  
}

type CatAnimal = Cat extends Animal ? Cat : Dog;  
let catAnimal: CatAnimal = <Cat>{  
  name: 'Joe',  
  kind: 'cat'  
}

In the code above, we created the CatAnimal type alias which is set to the Cat type if Cat extends Animal . Otherwise, it’s set to Dog . Since Cat does extend Animal , the CatAnimal type alias is set to the Cat type.

This means that in the example above if we change <Cat> to <Dog> like we do in the following code:

interface Animal {    
  kind: string;  
}

interface Cat extends Animal {  
  name: string;  
}

interface Dog {  
  name: string;  
}

type CatAnimal = Cat extends Animal ? Cat : Dog;  
let catAnimal: CatAnimal = <Dog>{  
  name: 'Joe',  
  kind: 'cat'  
}

We would get the following error message:

Property 'kind' is missing in type 'Dog' but required in type 'Cat'.(2741)

This ensures that we have the right type for catAnimal according to the condition expressed in the type. If we want to Dog to be the type for catAnimal , then we can write the following instead:

interface Animal {    
  kind: string;  
}

interface Cat  {  
  name: string;  
}

interface Dog extends Animal {  
  name: string;  
}

type CatAnimal = Cat extends Animal ? Cat : Dog;  
let catAnimal: CatAnimal = <Dog>{  
  name: 'Joe'  
}

We can also have nested conditions to determine the actual type from multiple conditions. For example, we can write:

interface Animal {    
  kind: string;  
}

interface Bird  {  
  name: string;  
}

interface Cat  {  
  name: string;  
}

interface Dog extends Animal {  
  name: string;  
}

type AnimalTypeName<T> =  
  T extends Animal ? Cat :      
  T extends Animal ? Dog :      
  T extends Animal ? Bird :  
  Animaltype t0 = AnimalTypeName<Cat>;    
type t1 = AnimalTypeName<Dog>;  
type t2 = AnimalTypeName<Animal>;  
type t3 = AnimalTypeName<Bird>;

Then we get the following types for the type alias t0 , t1 , t2 , and t3 :

type t0 = Animal  
type t1 = Cat  
type t2 = Cat  
type t3: Animal

The exact doesn’t have to be chosen immediately, we can also have something like:

interface Foo {}

interface Bar extends Foo {  
    
}

function bar(x) {  
  return x;  
}

function foo<T>(x: T) {  
  let y: T extends Foo ? string : number = bar(x);  
  let z: string | number = y;  
}

foo<Bar>(1);  
foo<Bar>('1');  
foo<Bar>(false);

As we can see we can pass in anything into the foo even though we have the conditional types set. This is because the actual type in the type condition hasn’t been chosen yet., so TypeScript doesn’t make any assumption about what we can assign to the variables in the foo function.

Distributive Conditional Types

Conditional types are distributive. If we have multiple conditional types that can possibly extend one type as we have in the following code:

interface A {}  
interface B {}  
interface C {}  
interface D {}  
interface X {}  
interface Y {}type TypeName = (A | B | C) extends D ? X : Y;

Then the last line is equivalent to:

(A extends D ? X : Y) | (B extends D ? X : Y) | (C extends D ? X : Y)

For example, we can use it to filter out types with various conditions. For example, we can write:

type Diff<T, U> = T extends U ? never : T;

To remove types from T that are assignable to U . If T extends U, then the Diff<T, U> type is never, which means that we can assign anything to it, otherwise it takes on the type T. Likewise, we can write:

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

to remove types from T that aren’t assignable to U . In this case, if T extends U, then the Filter type is the same as the T type, otherwise, it takes on the never type. For example, if we have:

type Diff<T, U> = T extends U ? never : T;  
type TypeName = Diff<string| number | boolean, boolean>;

Then TypeName has the type string | number . This is because Diff<string| number | boolean, boolean> is the same as:

(string extends boolean ? never : string) | (number extends boolean ? never: number) | (boolean extends boolean ? never: boolean)

On the other hand, if we write:

type Filter<T, U> = T extends U ? T : never;  
type TypeName = Filter<string| number | boolean, boolean>;

Then TypeName has the boolean type. This is because Diff<string| number | boolean, boolean> is the same as:

(string extends boolean ? string: never) | (number extends boolean ? number: never) | (boolean extends boolean ? boolean: never)

Predefined Conditional Types

TypeScript 2.8 has the following predefined conditional types, They’re the following:

  • Exclude<T, U> – excludes from T those types that are assignable to U.
  • Extract<T, U> – extract from T those types that are assignable to U.
  • NonNullable<T> – exclude null and undefined from T.
  • ReturnType<T> – get the return type of a function type.
  • InstanceType<T> – get the instance type of a constructor function type.

Since TypeScript 2.8, we can define types with conditional tests. The general expression for defining a conditional type in TypeScript is T extends U ? X : Y . They’re distributive, so (A | B | C) extends D ? X : Y; is the same as (A extends D ? X : Y) | (B extends D ? X : Y) | (C extends D ? X : Y) .