TypeScript Tutorial For Beginners: The Missing Guide - Part 3
Tags: typescript, javascript, beginners
Published: 2019-09-27 07:06:59

---
title: "TypeScript Tutorial For Beginners: The Missing Guide - Part 3"
published: true
description: "What is TypeScript and why you may want to use it? Learn more with this TypeScript tutorial for beginners and start adding types to your JavaScript code!"
tags: typescript, javascript, beginners
canonical_url: https://dev.to/valentinogagliardi/typescript-tutorial-for-beginners-the-missing-guide-part-2-3ahh
series: "TypeScript Tutorial For Beginners"
cover_image: https://thepracticaldev.s3.amazonaws.com/i/dvepdqnprwn7kzst9v5a.png
---

**Just crossed 5k follower on dev.to! Thank you everyone! What a fantastic community! Who's on Twitter too? Let's connect => [I'm here](https://twitter.com/gagliardi_vale)**.

_What is TypeScript and why you may want to use it? Learn more with this TypeScript tutorial for beginners and start adding types to your JavaScript code!_

_Originally published on [valentinog.com/blog](https://www.valentinog.com/blog/typescript/)_

## In this episode:

- interfaces extends
- indexing for interfaces
- typing return values


## TypeScript tutorial for beginners: extending interfaces

TypeScript interfaces are great. There will come a time however when you'll need a **new entity in your code which happens to be almost the same as another existing interface**. Let's say we want a **new interface named IPost** with the following properties:

- id, number
- title, string
- body, string
- url, string
- description, string

Description, id, and url ... looks like we already have the ILink interface with those very same properties:

```typescript
interface ILink {
  description?: string;
  id?: number;
  url: string;
}
```

Is there a way to **reuse the interface ILink**? Turns out in TypeScript we can **extend an interface** by assigning its properties to a new interface, so that IPost for example "inherits" some traits from ILink. Here's how to do it, notice the keyword **extends**:

```typescript
interface ILink {
  description?: string;
  id?: number;
  url: string;
}

interface IPost extends ILink {
  title: string;
  body: string;
}
```

Now any object of type IPost will have the optional properties description, id, url, and the required properties title and body:

```typescript
interface ILink {
  description?: string;
  id?: number;
  url: string;
}

interface IPost extends ILink {
  title: string;
  body: string;
}

const post1: IPost = {
  description:
    "TypeScript tutorial for beginners is a tutorial for all the JavaScript developers ...",
  id: 1,
  url: "www.valentinog.com/typescript/",
  title: "TypeScript tutorial for beginners",
  body: "Some stuff here!"
};
``` 

When an object like post1 uses an interface we say that **post1 implements the properties defined in that interface**. The interface on the other hand **has implementations** when it's used for describing one or more objects in your code.

**Extending an interface means borrowing its properties and widening them** for code reuse. But wait, there's more! As you'll see soon **TypeScript interfaces can also describe functions**. 

But first let's take a look at **indexing**!

## TypeScript tutorial for beginners: the indexing interlude

[**JavaScript objects**](https://www.valentinog.com/blog/objects/) are containers for key/value pairs. Imagine a simple object:

```javascript
const paolo = {
  name: "Paolo",
  city: "Siena",
  age: 44
};
```

We can access the value of any key with the dot syntax:

```javascript
console.log(paolo.city);
```

or with the bracket syntax (the same is true for JavaScript arrays since arrays are a special kind of object):

```javascript
console.log(paolo["city"]);
```

Now imagine that the **key becomes dynamic**, so that we can put it in a variable and reference it inside brackets:

```javascript
const paolo = {
  name: "Paolo",
  city: "Siena",
  age: 44
};

const key = "city";

console.log(paolo[key]);
```

Now let's add another object, put both inside an array, and filter the array with the **filter method** like we did in filterByTerm.js. But **this time the key is passed dynamically so filtering any object key becomes possible**:

```javascript
const paolo = {
  name: "Paolo",
  city: "Siena",
  age: 44
};

const tom = {
  name: "Tom",
  city: "Munich",
  age: 33
};

function filterPerson(arr, term, key) {
  return arr.filter(function(person) {
    return person[key].match(term);
  });
}

filterPerson([paolo, tom], "Siena", "city");
```

Here's the relevant line:

```javascript
return person[key].match(term);
```

Will it work? Yes because **JavaScript does not care whether paolo or tom are "indexable" with a dynamic \[key\]**. And what about TypeScript? Will it give an error in this case?

Let's find out: in the next section we'll **make filterByTerm more dynamic with a variable key**.

## TypeScript tutorial for beginners: interfaces can have indexes

Let's get back to **filterByTerm.ts** and in particular to our **filterByTerm function**:

```typescript
function filterByTerm(input: Array<ILink>, searchTerm: string) {
  if (!searchTerm) throw Error("searchTerm cannot be empty");
  if (!input.length) throw Error("input cannot be empty");
  const regex = new RegExp(searchTerm, "i");
  return input.filter(function(arrayElement) {
    return arrayElement.url.match(regex);
  });
}
```

It does not look so flexible because for every ILink we match the hardcoded property "url" against a regular expression. We may want to make the property, thus the key, dynamic. Here's a first try:

```typescript
function filterByTerm(
  input: Array<ILink>,
  searchTerm: string,
  lookupKey: string = "url"
) {
  if (!searchTerm) throw Error("searchTerm cannot be empty");
  if (!input.length) throw Error("input cannot be empty");
  const regex = new RegExp(searchTerm, "i");
  return input.filter(function(arrayElement) {
    return arrayElement[lookupKey].match(regex);
  });
}
```

lookupKey is the dynamic key, which gets also assigned a [**default parameter**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters) as a fallback, the string "url". Let's compile the code:

```bash
npm run tsc
```

Does it compile?

```text
error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'ILink'.
  No index signature with a parameter of type 'string' was found on type 'ILink'.
```

Here's the offending line:

```javascript
return arrayElement[lookupKey].match(regex);
```

"No index signature". Wow. JavaScript is loose while **TypeScript** on the other hand **does care and requires you to add an index** to the object's interface. It's an "easy" fix.

Head over the interface ILink and add the index:

```typescript
interface ILink {
  description?: string;
  id?: number;
  url: string;
  [index: string]: string;
}
```

The syntax is kind of weird but is similar to the dynamic key access on our objects. It means we can access **any key of that object through an index of type string**, which in turn returns another string.

Anyway, that first try will make other errors pop up like:

```text
error TS2411: Property 'description' of type 'string | undefined' is not assignable to string index type 'string'.
error TS2411: Property 'id' of type 'number | undefined' is not assignable to string index type 'string'.
```

That's because some properties on the interface are optional, maybe undefined, and not always of type string (id is a number for example). 

We can try to fix the problem with a **union type**, a TypeScript syntax for defining types that are the **union between two or more other types**:

```typescript
interface ILink {
  description?: string;
  id?: number;
  url: string;
  [index: string]: string | number | undefined;
}
```

The following line:

```typescript
[index: string]: string | number | undefined;
```

means that **index is a string and may return another string, a number, or undefined**. Try to compile again and here's another error:

```text
error TS2339: Property 'match' does not exist on type 'string | number'.
return arrayElement[lookupKey].match(regex);
```

Makes sense. The [match method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match) works only for strings and there's a chance that our index will return a number. To fix the error for good we can use the **any** type without any (no pun intended) regret:

```typescript
interface ILink {
  description?: string;
  id?: number;
  url: string;
  [index: string]: any;
}
```

The TypeScript compiler is happy again! Yes! 

And now it's time to turn our attention to another fundamental TypeScript feature: **return types for functions**.

## TypeScript tutorial for beginners: return types for functions

It's been a lot of new stuff up until now. Anyway, I skipped over another TypeScript's useful feature: **return types for functions**. 

To understand why it's handy to add a type annotation for return values imagine me, messing with your fancy function. Here's the **original version**:

```typescript
function filterByTerm(
  input: Array<ILink>,
  searchTerm: string,
  lookupKey: string = "url"
) {
  if (!searchTerm) throw Error("searchTerm cannot be empty");
  if (!input.length) throw Error("input cannot be empty");
  const regex = new RegExp(searchTerm, "i");
  return input.filter(function(arrayElement) {
    return arrayElement[lookupKey].match(regex);
  });
}
```

If called as it is, passing in the array of ILink you saw earlier and the search term "string3", it should return an array of objects, as expected:

```javascript
filterByTerm(arrOfLinks, "string3"); 

// EXPECTED OUTPUT:
// [ { url: 'string3' } ]
```

But now consider an **altered variant**:

```typescript
function filterByTerm(
  input: Array<ILink>,
  searchTerm: string,
  lookupKey: string = "url"
) {
  if (!searchTerm) throw Error("searchTerm cannot be empty");
  if (!input.length) throw Error("input cannot be empty");
  const regex = new RegExp(searchTerm, "i");
  return input
    .filter(function(arrayElement) {
      return arrayElement[lookupKey].match(regex);
    })
    .toString();
}
```

If called now, with the same array of ILink and the search term "string3", it returns ... **\[object Object\]**!:

```javascript
filterByTerm(arrOfLinks, "string3");

// WRONG OUTPUT:
// [object Object]
```

Can you spot the problem? Hint: **toString** ;-)

The **function is not working as expected** and **you would never know unless hitting production (or testing your code)**. Luckily TypeScript can catch these errors, as you write in the editor.

Here's the fix (just the relevant portion):

```typescript
function filterByTerm(/* omitted for brevity */): Array<ILink> {
 /* omitted for brevity */
}
```

How it works? **By adding type annotations before the function's body we tell TypeScript to expect another Array<ILink> as the return value**. Now the bug can be spotted easily. Here's the code so far (**altered version**):

```typescript
interface ILink {
  description?: string;
  id?: number;
  url: string;
  [index: string]: any;
}

function filterByTerm(
  input: Array<ILink>,
  searchTerm: string,
  lookupKey: string = "url"
): Array<ILink> {
  if (!searchTerm) throw Error("searchTerm cannot be empty");
  if (!input.length) throw Error("input cannot be empty");
  const regex = new RegExp(searchTerm, "i");
  return input
    .filter(function(arrayElement) {
      return arrayElement[lookupKey].match(regex);
    })
    .toString();
}

const obj1: ILink = { url: "string1" };
const obj2: ILink = { url: "string2" };
const obj3: ILink = { url: "string3" };

const arrOfLinks: Array<ILink> = [obj1, obj2, obj3];

filterByTerm(arrOfLinks, "string3");
```

Now compile it:

```bash
npm run tsc
```

and check out the error:

```text
error TS2322: Type 'string' is not assignable to type 'ILink[]'.
```

Fantastic. We're expecting an array of links, not a string. **To fix the error remove .toString() from the end of filter** and compile the code again. It should work now!

We added another layer of protection to our code. Granted, that bug could have been spotted with a [unit test](https://www.valentinog.com/blog/jest/). **TypeScript** in fact **is a nice layer of safety rather than a complete replacement for testing**.

Let's continue the exploration with **type aliases**!

******************************************************************

**Stay tuned for part 4!**