Categories
JavaScript TypeScript

Introduction to TypeScript Interfaces — Enforcing Class Implementation

The big advantage of TypeScript over plain JavaScript is that it extends the features of JavaScript by adding type safety to our program’s objects. It does this by checking the shape of the values that an object takes on. Checking the shape is called duck typing or structural typing.

Interfaces are one way to fill the role of naming data types in TypeScript. It’s very useful for defining a contract within our code in TypeScript programs. In this article, we will look at how we use interfaces for enforcing the implementation of classes.

Class Types

We can use TypeScript interfaces to make sure classes meet a specific contract as specified by interfaces like in other programming languages such as C# and Java. For example, we can define a class that implements the items specified by the interface with the implements keyword like we do in the code below:

interface PersonInterface {  
  name: string;  
}

class Person implements PersonInterface {  
  name: string = 'Mary';      
}

In the code above, we implemented the class Person which implements the items outlined in the PersonInterface. Since the PersonInterface has the field name which is a string, we follow that contract in the Person class that we implemented below that. The code above implements all the items outlined in the interface, so the TypeScript compiler will accept the code above as being valid. If we didn’t implement the items in the interface but we used the implements keyword, then we’ll get an error:

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

class Person implements PersonInterface {      
  name: string = 'Mary';    
  foo: any = 'abc';  
}

For example, if we have the code above, then the TypeScript compiler will reject it with an error since we only have the name field in the Person, but instead of the age field, we have foo instead. So if we try to compile the code above, we would get an error message “Class ‘Person’ incorrectly implements interface ‘PersonInterface’. Property ‘age’ is missing in type ‘Person’ but required in type ‘PersonInterface’.”

However, after we implemented all the items in the interface, we can add extra items that aren’t specified in the interface and the TypeScript compiler would accept the code:

interface PersonInterface {  
  name: string;      
}

class Person implements PersonInterface {      
  name: string = 'Mary';    
  age: number = 20;  
}

The code above would be accepted by the TypeScript compiler since we have all the things in the interface and we added stuff to the class which isn’t in the interface, which is acceptable in TypeScript.

We can also describe methods that we will implement in classes with an interface. To do this, we can add method signatures along with their return type in our interfaces, like in the code below:

interface PersonInterface {  
  name: string;  
  age: number;  
  setName(name: string): string;  
  setAge(age: number): number;  
}

class Person implements PersonInterface {      
  name: string = 'Mary';    
  age: number = 20;  
  setName(name: string) {  
    this.name = name;  
    return name;  
  } 

  setAge(age: number) {  
    this.age = age;  
    return age;  
  }  
}

In the code above, we put 2 method signatures that are to be implemented in the class that implements the interface in the PersonInterface. We have the method signature for the setName method and another one for the setAge method. Inside the parentheses of each we specified the required parameters for each method. In the setName method, we specified that we require the name parameter and is of the type string. In the setAge method, we specified that we have an age parameter and is of type number. That’s the part on the left of the colon. On the right of the colon, we have the return type of each method. For the setName method, we specified that it returns a string. In the signature of the setAge method, we specified that it returns a number.

Then in the Person class, we just following the implementation as it’s outlined in the interface. So we added the name and age fields, which we designate as type string and number respectively. Then we add the setName and setAge methods as we outlined in the PersonInterface. Note that the type designation in the parameters of the methods are required, but the return type is not since it can be inferred by TypeScript but the parameter types have to checked explicitly. This means that if we have something like the following code with the parameter type omitted:

interface PersonInterface {  
  name: string;  
  age: number;  
  setName(name: string): string;  
  setAge(age: number): number;  
}

class Person implements PersonInterface {      
  name: string = 'Mary';    
  age: number = 20;  
  setName(name) {  
    this.name = name;  
    return name;  
  } 

  setAge(age) {  
    this.age = age;  
    return age;  
  }  
}

Then the TypeScript compiler will reject the code with the error messages “Parameter ‘name’ implicitly has an ‘any’ type.(7006)” for the setName method and “Parameter ‘age’ implicitly has an ‘any’ type.(7006)”. As we can see from the errors, parameters that have no type designation attached to it will be implicitly designated the any type, which isn’t what’s specified in the PersonInterface .

Interfaces only describe the public side of classes, rather than both the public and private side. This means that we can’t use it for checking private fields and methods.

Static Side vs Instance Side of Classes

In TypeScript, interfaces only check the instance side of classes, so anything that’s related to the static side of classes isn’t checked. For example, if we have the following code:

interface PersonInterface {  
  new (name: string, age: number);  
}

class Person implements PersonInterface {      
  constructor(name: string, age: number) {}  
}

We would get the error “Class ‘Person’ incorrectly implements interface ‘PersonInterface’. Type ‘Person’ provides no match for the signature ‘new (name: string, age: number)” since the static side of a class can’t be checked by an interface, and the constructor is on the static side. Instead we have to add different interfaces for each part of the static side. We can do this like in the code below:

interface PersonConstructor {  
  new (name: string, age: number): PersonInterface;  
}

interface PersonInterface {  
  name: string;  
  age: number;  
  greet(name: string): void;  
}

const createPerson = (ctor: PersonConstructor, name: string, age: number): PersonInterface =>{  
  return new ctor(name, age);      
}

class Person implements PersonInterface {      
  name: string;  
  age: number;  
  constructor(name: string, age: number) {  
    this.name = name;  
    this.age = age;  
  }  
  greet(name: string) {  
    console.log(`Hello ${this.name}. You're ${this.age} years old.`)  
  }  
}

let person = createPerson(Person, 'Jane', 20);  
console.log(person);

With the code above, the PersonInterface only has the public members, which are name , age , and the greet method. This forces us to implement these members in our Person class. The constructor isn’t checked by the PersonInterface since it’s a static member. We leave the check for that in the createPerson function. In createPerson, we check that the ctor parameter implements the PersonConstructor correctly, since we have the new method with the name and age parameters in the signature and we checked that it returns a PersonInterface which means that the constructor is returning an instance of some class that implements the PersonInterface . The code above will be accepted by the TypeScript compiler since we checked the static side separately from the instance side since we created the createPerson to let us check the static side, which is the constructor .

Another powerful feature of TypeScript is that we can use interfaces to check what we implemented in our classes by using interfaces. It’s useful for checking non-static members of a class. However, for static members like constructor , we have to check them outside the interface that we use for implementing classes since they can’t check for static members like the constructor method.

Categories
JavaScript TypeScript Vue

Writing Vue.js Apps with TypeScript

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at how to write Vue.js apps with TypeScript.

Support for TypeScript

Vue.js has built-in support for TypeScript. It provides us with a mostly static type system that reduces the chances for run-time errors in our code.

Vue ships with official type declarations for TypeScript. It’s included in Vue, Vue Router and Vuex.

TypeScript can resolve types declarations in NPM packages, so we don’t have to worry about them resolving them ourselves.

Using Vue CLI

We can use the Vue CLI to create a Vue TypeScript project. To do this, we have to run Vue CLI by running:

npx vue create .

in our project directory.

Then we select ‘Manually select features’ and then TypeScript. The rest can stay with the default options.

Then we run npm run serve to run the project hot reload.

If we want, we can also set the following recommended options in tsconfig.json :

{  
  "compilerOptions": {  
    "target": "es5",  
    "strict": true,  
    "module": "es2015",  
    "moduleResolution": "node"  
  }  
}

target is the build target. Setting it to es5 maximizes support for browsers.

strict enables or disables strict type check. Setting it to true will enable it.

“module”: “es2015” leverage tree shaking capabilities of Webpack 2+ or Rollup to reduce bundle size and speed up loading.

Defining and Using Components

We can define components by using the class syntax. For example, with the TypeScript project that we created with Vue CLI, we can do this as follows:

src/components/Message.vue :

<template>  
  <div class="hello">  
    <h1>{{ msg }}</h1>  
  </div>  
</template><script lang="ts">  
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component 
export default class Message extends Vue {  
  @Prop() private msg!: string;  
}  
</script>

In the code above, we have the @Prop decorator to define the message prop. The exclamation mark after msg makes msg required. To the right of the colon, we have the type of the prop, which is a string.

src/App.vue :

<template>  
  <div id="app">  
    <input v-model="message">  
    <Message :msg="message"/>  
    <button @click="showMessage">Show Message</button>  
  </div>  
</template>

<script lang="ts">  
import { Component, Vue } from 'vue-property-decorator';  
import Message from './components/Message.vue';

@Component({  
  components: {  
    Message,  
  },  
})  
export default class App extends Vue {  
  message: string = ''; showMessage(): void{  
    alert(this.message)  
  }  
}  
</script><style>  
#app {  
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif  
}  
</style>

In the code above, we defined the message field, which is bound to the template with v-model .

We also have a showMessage method that’s run when the Show Message button is clicked.

Then we can see an input box where we can type things in. What we type will show in below the input box in the Message component, and when we click Show Message, we get what we typed is shown in the alert box.

Use Vue.extend to Create Components

We can also use Vue.extend to create components.

For example, we can replace src/components/Message.ts with the following”

import Vue, { VNode } from 'vue'
const Component = Vue.extend({  
  props: ['msg'],  
  computed: {  
    greeting(): string {  
      return `Hi. ${this.msg}`;  
    }  
  },  
  render(createElement): VNode {  
    return createElement('div', [  
      createElement('p', this.msg),  
      createElement('p', this.greeting),  
    ])  
  }  
})export default Component;

In the code above, we render a div with the msg prop and greeting computed property.

Note that we have VNode return type for render . This will ensure that we return the right type of data. TypeScript can’t infer types in many cases.

Since we use Vue.extend , we’ll get type inference and autocomplete.

We have to export by writing:

export default Component;

so that it’ll be available to other components.

Then we can import it as usual in src/App.vue :

<template>  
  <div id="app">  
    <input v-model="message">  
    <Message :msg="message"/>  
    <button @click"showMessage">Show Message</button>  
  </div>  
</template><script lang="ts">  
import { Component, Vue } from 'vue-property-decorator';  
import Message from './components/Message';

@Component({  
  components: {  
    Message,  
  },  
})  
export default class App extends Vue {  
  message: string = ''; showMessage(): void{  
    alert(this.message)  
  }  
}  
</script><style>  
#app {  
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif  
}  
</style>

Then we the input where we can type in something and it’ll show up with and without Hi added before it.

We keep the Show Message button acting the way before.

Augmenting Types for Use with Plugins

We can add our own properties to existing types by writing the following:

import Vue from 'vue'declare module 'vue/types/vue' {  
  interface Vue {  
    $myProperty: string  
  }  
}

The code above imports Vue and then add the $myProperty string property as a valid property.

This should make TypeScript compiler be aware of the code and it should compile successfully.

Conclusion

We can create a new project with TypeScript by using the Vue CLI. To do this, we choose Manually Select Features and then TypeScript. We can keep the default option for the rest or we can choose what we want if we know we want it.

To get type inference and autocomplete, we can define components with the Vue.extend method or define it as a class.

We can augment existing Vue types by using the declare module syntax and adding what we want inside it.

TypeScript can’t infer return types of functions in many cases. Therefore, we should annotate the return type so that we get autocomplete and type checking.

Categories
JavaScript TypeScript

Manipulate JavaScript Arrays the Functional Way

Functional programming is a programming paradigm which states that we create computations as the evaluation of functions and avoid changing state and mutable data.

To write programs in a functional way, we use pure functions and immutable data structures.

In JavaScript, we can easily apply functional principles to arrays by using its built-in array methods.

In this article, we’ll look at the array methods that let us manipulate them in a functional way.

Filtering Items

We can use the filter method to return an array with entries from the original array that has the condition that the callback of the filter method returns.

For example, if we have the following array:

const store = [{  
    name: "Apple",  
    price: 1  
  },  
  {  
    name: "Banana",  
    price: 2  
  },  
  {  
    name: "Grape",  
    price: 1.2  
  }  
];

We can get the items that have price less than 2 as follows:

const cheapItems = store.filter(s => s.price < 2);

Then we get the following for cheapItems:

[  
  {  
    "name": "Apple",  
    "price": 1  
  },  
  {  
    "name": "Grape",  
    "price": 1.2  
  }  
]

Using the filter method fits with the functional paradigm since the function is pure, given the same array and same condition, we always get the same results returned.

It also doesn’t change the existing elements of the array that it’s called on, which means we can’t accidentally change anything from the original array.

Mapping Array Objects

map is used to map entries of an array into something else. It’s frequently used to convert and transform data. Using map fits with the functional programming paradigm because again it’s a pure function that gives the same outputs for the given inputs, and it doesn’t change the original array.

As long as the function we pass into the map function to combine the values is pure, the map method should also be pure since it just calls this callback function and returns the updated values into a new array.

For example, given the following array:

const volumesInLiters = [{  
    name: "Milk",  
    volumeInLiters: 1  
  },  
  {  
    name: "Apple Juice",  
    volumeInLiters: 2  
  },  
  {  
    name: "Orange Joice",  
    volumeInLiters: 1.2  
  }  
];

We can add the volumeInQuarts field to each entry of the object and set the volume in quarts to as the value of each by writing:

const volumesInQuarts = volumesInLiters.map(v => {  
  v = {  
    ...v,  
    volumeInQuarts: v.volumeInLiters * 1.057  
  };  
  return v;  
})

We convert v.volumeInLiters * 1.057 and set it to volumeInQuarts for each entry.

Then we get:

[  
  {  
    "name": "Milk",  
    "volumeInLiters": 1,  
    "volumeInQuarts": 1.057  
  },  
  {  
    "name": "Apple Juice",  
    "volumeInLiters": 2,  
    "volumeInQuarts": 2.114  
  },  
  {  
    "name": "Orange Joice",  
    "volumeInLiters": 1.2,  
    "volumeInQuarts": 1.2684  
  }  
]

Using Reduce to Combine Values of Array Entries

Like filter and map, reduce also fits with the functional paradigm since we use it to gather entries of an array and return one value by the way that we specify.

As long as the function we pass into the reduce function to combine the values is pure, the reduce method should be pure. The function just calls the callback function we pass into reduce to compute the combined value.

For example, given the following array:

const store = [{  
    name: "Apple",  
    price: 1  
  },  
  {  
    name: "Banana",  
    price: 2  
  },  
  {  
    name: "Grape",  
    price: 1.2  
  }  
];

We can write the following to get the total price of all items:

const totalPrice = store.map(s => s.price).reduce((subtotal, b) => subtotal + b, 0);

We have to use map to map all the entries in the store array to price numbers. Then we can use the reduce method to add the new value to subtotal.

Then we should get 4.2 for totalPrice, which is the total of all the prices of the 3 items in store.

The second argument of reduce is the starting value of the reduction, or combining the values into one.

Conclusion

The filter, map, and reduce array methods are the main methods for manipulating arrays in a functional manner. They all return values and don’t change the array that it’s called on. This makes it difficult to unintentionally change the values.

filter returns an array that meets the condition that we return in the callback that we pass in.

map returns a new array that has the values of the original array mapped to new values in a way that we specify.

reduce combines the values of array entries into one by taking a callback function that we pass in to do the combining and returns the combined value.

The functions are pure if we pass in pure functions to them as callbacks since they just call the functions that we pass in to do the manipulation.

Categories
JavaScript TypeScript

Merging Declarations in TypeScript

With TypeScript, we can declare many entities that don’t exist in vanilla JavaScript like interfaces, enums, and type alias. Sometimes, these types have to be merged together into one, like when we try to derive a new class from multiple classes or interfaces. This means that there are rules for merging them together. The process of declaration merging is the combining of multiple members from different sources with overlapping member names or types.

Basic Rules of Merging Interfaces

Merging interfaces is the simplest and most common operation type of declaration merging. The most basic case is that 2 interfaces have no overlapping members. In this case, they just get merged together mechanically into one. For example, if we have 2 Person interfaces like we do in the following code:

interface Person {  
  name: string;  
}

interface Person {  
  age: number;  
}

Then they’ll just be merged together into one when we declare a variable with the Person type. For example, we can write something like:

let person: Person = {  
  name: 'Jane',  
  age: 20  
}

Then TypeScript would accept this code and compile and run the code.

If there’re any overlap of non-function members, then they must be unique. Otherwise, they must have the same type. For example, if we have:

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

interface Person {  
  age: number;  
}

Then we get the following errors:

Subsequent property declarations must have the same type.  Property 'age' must be of type 'string', but here has type 'number'.(2717)input.ts(3, 5): 'age' was also declared here.

Also when 2 interfaces are being merged, whatever is in the second interface will overwrite the members of the first one if the first one has overlapping members with the second one. For function members, they’ll just be combined together as overloads of the same function. For example, if we have the following interface declarations:

interface Animal { };  
interface Sheep { };  
interface Dog { };  
interface Cat { };

interface Eater {  
  eat(animal: Animal): Animal;  
}

interface Eater {  
  eat(animal: Sheep): Sheep;  
}

interface Eater {  
  eat(animal: Dog): Dog;  
  eat(animal: Cat): Cat;  
}

Then we get the Eater interface, would be:

interface Eater {    
  eat(animal: Dog): Dog;  
  eat(animal: Cat): Cat;  
  eat(animal: Sheep): Sheep;  
  eat(animal: Animal): Animal;  
}

The elements of each group maintain the same order but the group of functions that are in the later interfaces is ordered first in the merged interface. The only exception is that when the signature of a function member is composed of a single string literal, then that’ll be moved to the top. For example, if we have:

interface Animal { };  
interface Sheep { };  
interface Dog { };  
interface Cat { };
interface Eater {  
  eat(animal: 'Animal'): Animal;  
}

interface Eater {  
  eat(animal: Sheep): Sheep;  
}

interface Eater {  
  eat(animal: Dog): Dog;  
  eat(animal: Cat): Cat;  
}

Then the merged Eater interface would be:

interface Eater {    
  eat(animal: 'Animal'): Animal;  
  eat(animal: Dog): Dog;  
  eat(animal: Cat): Cat;  
  eat(animal: Sheep): Sheep;  
}

Namespaces

Namespaces of the same name will have their members merged together. All the exported interfaces and classes are merged into one namespace. For example, if we have the following namespaces:

namespace Animals {  
  export class Cat { }  
}

namespace Animals {  
  export interface Mammal {  
      name: string;          
  }  
  export class Dog { }  
}

Then the merged namespace will be:

namespace Animals {  
  export interface Mammal {  
      name: string;          
  } 
  export class Cat { }  
  export class Dog { }  
}

Non-exported members, on the other hand, are only visible in their own un-merged namespace. For example, if we have:

namespace Animal {  
  export class Cat { }  
  export const getAnimal = () => {  
    return animal;  
  }  
}

namespace Animal {  
  let animal = {};  
  export interface Mammal {  
    name: string;          
  }  
  export class Dog { }  
}

Then we get the following error messages:

Cannot find name 'animal'. Did you mean 'Animal'?(2552)

since the animal variable isn’t exported.

We can’t have 2 namespace members with the same name since we would have duplicate declarations that aren’t allowed. For example, if we have:

namespace Animal {  
    export class Cat { }  
    export let animal = {};  
    export const getAnimal = () => {  
        return animal;  
    }  
}

namespace Animal {  
    export let animal = {};  
    export interface Mammal {  
      name: string;          
    }  
    export class Dog { }  
}

Then we get the error:

Cannot redeclare block-scoped variable 'animal'.(2451)

Merging Namespaces with Classes

The rules for merging namespaces with classes is the same as merging any other namespace members. They have to be exported for us to merge them. We can write something like the following to define inner classes by referencing a class inside a namespace from an outer class:

class Person {  
    label: Person.PersonName;      
}

namespace Person {  
    export class PersonName { }  
}

We can also reference members from a namespace from a function that has the same name as the namespace. For example, we can write the following code:

function buildName(middleName: string): string {  
    return `${buildName.firstName} ${middleName} ${buildName.lastName}`;  
}

namespace buildName {  
    export let firstName = "Jane";  
    export let lastName = "Smith";  
}
buildName('Mary');

We can access the members of the buildName namespace inside the buildName function as long as we export the members. Likewise we can reference enum members from inside a namespace with the same name as the enum. For example, we can write something like:

enum Fruit {  
  Orange = 1,  
  Apple = 2,  
  Banana = 4  
}

namespace Fruit {  
  export const mixFruit = (fruit: string) => {  
    if (fruit == "Orange and Apple") {  
      return Fruit.Orange + Fruit.Apple;  
    }  
    else if (fruit == "Apple and Banana") {  
      return Fruit.Apple + Fruit.Banana  
    }  
    return 0;  
  }  
}

As we can see, we can access the members of the Fruit enum in the Fruit namespace inside the mixFruit function.

Disallowed Merges

Currently, we can merge classes with other classes or variables.

Module Augmentation

We can use the declare keyword to declare that a module has members with properties that the TypeScript compiler doesn’t know about. For example, we can write something like:

// fruit.ts  
export class Fruit<T> {  
  // ...  
}

// setColor.ts

import { Fruit } from "./fruit";  
declare module "./fruit" {  
  interface Fruit<T> {  
    setColor(f: (x: string) => string): Fruit;  
  }  
}  
Fruit.prototype.setColor = function (f) {  
  // ...  
}

In the code above, we used the declare module keywords to declare the items in the Fruit class that the TypeScript compiler can’t see. It lets us manipulate things that are defined but TypeScript compiler can’t spot.

We can use the method above to patch existing declarations. It doesn’t work with new top-level declarations. Also, default exports can’t be augmented since it has no name.

Global Augmentation

We can also add declarations in the global scope from inside a module. For example, we can write something like the following code:

// fruit.ts  
export class Fruit {  
   
}

declare global {  
  interface Array<T> {  
    toFruitArray(): Fruit[];  
  }  
}

Array.prototype.toFruitArray = function () {  
  return this.map(a => <Fruit>{ ... });  
}

In the code above, we made the TypeScript compiler aware of the global Array object and then added a toFruitArray to its prototype. Without the declare clause, we would get an error since the TypeScript compiler doesn’t know that the toFruitArray method exists in the Array global object.

Many things can be merged together in TypeScript. Merging interfaces is the simplest and most common operations types of declaration merging. The most basic case is that 2 interfaces have no overlapping members. In this case, they just get merged together mechanically into one. Also when 2 interfaces are being merged, whatever is in the second interface will overwrite the members of the first one if the first one has overlapping members with the second one. For function members, they’ll just be combined together as overloads of the same function. Namespaces of the same name will have their members merged together. All the exported interfaces and classes are merged into one namespace.

We can use the declare keyword to declare that a module has members with properties that the TypeScript compiler doesn’t know about. We can also add declarations in the global scope from inside a module with global module augmentation. Classes and variables currently can’t be merged together.

Categories
JavaScript TypeScript

Introduction to TypeScript Generics — Classes

One way to create reusable code is to create code that lets us use it with different data types as we see fit. TypeScript provides the generics construct to create reusable components where we can work with a variety of types with one piece of code. This allows devs to use these components by putting in their own types. In this article, we’ll look at the many ways to define generic classes and also validate properties by defining interfaces and the extends keyword and also force the code to only accept property names that are in an object by using the keyof keyword.

Defining a Generic Class

To define generic classes in TypeScript, we put the generic type marker in between the <> after the class name. We can have more than one type marker separated by a comma. Also, we can use the same type marker to mark the type of the function to let us change parameter and return type of methods within our class. For example, we can write the following code to define a generic class with a single generic type marker:

class GenericPerson<T> {    
  name: T;  
  getName: (name: T) => T;  
}let person = new GenericPerson<string>();  
person.name = 'Jane';  
person.getName = function(x) { return x; };

In the code above, we passed in the string type inside the <> . We can also change to any other type we want like number, boolean, or any interface like in the following code:

class GenericPerson<T> {    
  name: T;  
  getName: (name: T) => T;  
}let person = new GenericPerson<number>();  
person.name = 2;  
person.getName = function(x) { return x; };

We can also insert multiple types within the same class as in the following code:

class GenericPerson<T, U> {    
  name: T;  
  getName: (name: U) => U;  
}let person = new GenericPerson<string, number>();  
person.name = 'Jane';  
person.getName = function (x) { return x; };  
person.getName(2);

In the code above, T and U may or may not be different types. As long as we put them both in and that we pass in and assign data with the types we specified in the class definition, then the TypeScript compiler will accept it. The examples above are only useful for a limited amount of cases since we can’t reference any properties in our class definition since the TypeScript compiler doesn’t know what properties T or U contains.

To make the TypeScript compiler aware of the properties that may be used in our class definition, we can use an interface to list all the properties that can be used with the class and the use the extends keyword after the generic type marker to denote the that type may have the list of properties that are listed in the interface. For example, if we want to have different complex types in our class methods, we can write something like the following code:

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

interface GreetingInterface {  
  toString(): string;  
}

class GenericPerson<T extends PersonInterface, U extends GreetingInterface> {    
  person: T;  
  greet(greeting: U): string {  
    return `${greeting.toString()} ${this.person.name}. You're ${this.person.age} years old`;  
  }  
}

let jane = new GenericPerson();  
jane.person = {  
  name: 'Jane',  
  age: 20  
};  
console.log(jane.greet('Hi'));

In the code above, we defined 2 interfaces, the PersonInterface and the GreetingInterface to denote the properties that can be referenced with T and U respectively. In the PersonInterface, we have the name and age properties listed so we can reference these properties for data with type T. For the data with U type we can call the toString method on it. Therefore, in our greet method, we can call toString on greeting since it has the U type and since this.person has the T type, we can get the name and age properties from it.

Then after the class definition, we can instantiate the class and then set values for the name and age properties on the jane object that we created. Then when we run console.log on jane.greet(‘Hi’) then we should see ‘Hi Jane. You’re 20 years old’ since we set the values for jane.person.

We can also put in the types explicitly when we instantiate the object to make the types more clear. Instead of what we have above, we can change it slightly and write the following code:

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

interface GreetingInterface {  
  toString(): string;  
}

class GenericPerson<T extends PersonInterface, U extends GreetingInterface> {    
  person: T;  
  greet(greeting: U): string {  
    return `${greeting.toString()} ${this.person.name}. You're ${this.person.age} years old`;  
  }  
}

let jane = new GenericPerson<PersonInterface, string>();  
jane.person = {  
  name: 'Jane',  
  age: 20  
};  
console.log(jane.greet('Hi'));

The only difference is that we add the <PersonInterface, string> after new GenericPerson. Note that we can add interfaces or primitive types in between the brackets. TypeScript only cares that the type has the methods listed in the interface we defined. Now that the types are constrained by these generic types, we don’t have to worry about referencing any unexpected properties. For example, if we reference something that doesn’t exist like in the following code:

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

interface GreetingInterface {  
  toString(): string;  
}

class GenericPerson<T extends PersonInterface, U extends GreetingInterface> {    
  person: T;  
  greet(greeting: U): string {  
    return `${greeting.foo()} ${this.person.name}. You're ${this.person.age} years old`;  
  }  
}

let jane = new GenericPerson<PersonInterface, string>();  
jane.person = {  
  name: 'Jane',  
  age: 20  
};  
console.log(jane.greet('Hi'));

We would get “Property ‘foo’ does not exist on type ‘U’.(2339)” since we didn’t list foo in our GreetingInterface .

Constraining Retrieval of Object Properties

TypeScript also provides a way to let us get the properties of an object safely with generics. We can use the keyof keyword to constrain the values of the than an object can take on to the key names of another object. For example, we can use the keyof keyword like in the following code:

function getProperty<T, K extends keyof T>(obj: T, key: K) {  
  return obj[key];  
}

let x = { foo: 1, bar: 2, baz: 3 };console.log(getProperty(x, "foo"));

In the code above, we constrain the generic marker K to only accept the keys of whatever object is passed in to obj as the valid values of key since K has the extends keyof T marker after K . This means that whatever keys the T type has, then the keys are valid values for K . With code like the ones above, we don’t have to worry about getting values for properties that don’t exist. So if we pass in a key name that doesn’t exist in obj , like in the following code:

getProperty(x, "a");

Then the TypeScript compiler will reject the code and give the error message “Argument of type ‘“a”’ is not assignable to parameter of type ‘“foo” | “bar” | “baz”’.(2345)“. This means that only 'foo' , 'bar' and 'baz' are valid values for key since it has the type K , which has the extends keyof T marker after it to constrain the valid uses to be the key names of obj .

We can define a generic class easily with TypeScript. To define generic classes in TypeScript, we put the generic type marker in between the <> after the class name. We can have more than one type marker separated by a comma. Also, we can use the same type marker to mark the type of the function to let us change parameter and return type of methods within our class. Also, we can use the extends keyword to define the properties that can be referenced with our data marked by generic types. We can use the keyof keyword to constrain the values of the than an object can take on to the key names of another object.