DEV Community

Cover image for TypeScript Tutorial For Beginners: The Missing Guide - Part 2
Valentino Gagliardi
Valentino Gagliardi

Posted on • Originally published at dev.to

TypeScript Tutorial For Beginners: The Missing Guide - Part 2

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.

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

In this episode:

  • TypeScript types
  • TypeScript interfaces
  • typing variables

Dipping our toes into TypeScript types

TypeScript revolves around types and looks like our code has no types at all. Time to add some. We're going to fix function parameters first. By looking at how the function is called it seems it takes strings as arguments:

filterByTerm("input string", "java");
Enter fullscreen mode Exit fullscreen mode

Are we sure? Let's add your first type annotation to the function. Here's how:

function filterByTerm(input: string, searchTerm: string) {
    // omitted
}

// omitted
Enter fullscreen mode Exit fullscreen mode

That's it! By adding types to the parameters we're migrating our code from pure JavaScript to TypeScript. But if you try to compile the code:

npm run tsc
Enter fullscreen mode Exit fullscreen mode

here's what happens:

filterByTerm.ts:5:16 - error TS2339: Property 'filter' does not exist on type 'string'.
Enter fullscreen mode Exit fullscreen mode

Can you see how TypeScript is guiding you? The problem is with the filter function:

function filterByTerm(input: string, searchTerm: string) {
    // omitted
  return input.filter(function(arrayElement) {
    return arrayElement.url.match(regex);
  });
}
Enter fullscreen mode Exit fullscreen mode

We're telling TypeScript that "input" is a string but later in the code we call the filter method on it, which belongs to arrays. What we really want instead is marking "input" as an array of something, maybe an array of strings?

For doing so you have two options. Option 1 with string[]:

function filterByTerm(input: string[], searchTerm: string) {
    // omitted
}
Enter fullscreen mode Exit fullscreen mode

or if you like this syntax, option 2 with Array:

function filterByTerm(input: Array<string>, searchTerm: string) {
    // omitted

}
Enter fullscreen mode Exit fullscreen mode

Personally I like option 2 more. Now let's try to compile again (npm run tsc) and here it is:

filterByTerm.ts:10:14 - error TS2345: Argument of type '"input string"' is not assignable to parameter of type 'string[]'.

filterByTerm("input string", "java");
Enter fullscreen mode Exit fullscreen mode

TypeScript doesn't want to leave us alone I suppose. Don't blame it, we marked input as an array of strings and now we're trying to pass in a string. That's an easy fix! Let's pass an array of strings instead:

filterByTerm(["string1", "string2", "string3"], "java");
Enter fullscreen mode Exit fullscreen mode

And here's the complete code so far:

function filterByTerm(input: Array<string>, 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);
  });
}

filterByTerm(["string1", "string2", "string3"], "java");
Enter fullscreen mode Exit fullscreen mode

Looks good to me. But if you compile it's not (npm run tsc):

filterByTerm.ts:6:25 - error TS2339: Property 'url' does not exist on type 'string'.
Enter fullscreen mode Exit fullscreen mode

Ok TypeScript, fair enough. We're passing in an array of strings but later in the code we try to access a property named "url":

return arrayElement.url.match(regex);
Enter fullscreen mode Exit fullscreen mode

That means we want an array of objects, not an array of strings. Let's fix that into the next section!

TypeScript tutorial for beginners: TypeScript objects and interfaces

We left with TypeScript complaining (what a surprise) because filterByTerm has been passed an array of strings. "url" property does not exists on type string TypeScript yelled. Let's help TypeScript then by passing an array of objects, where every object has the required url property:

filterByTerm(
  [{ url: "string1" }, { url: "string2" }, { url: "string3" }],
  "java"
);
Enter fullscreen mode Exit fullscreen mode

and while you're there update the function signature so that it takes an array of objects:

function filterByTerm(input: Array<object>, searchTerm: string) {
    // omitted
}
Enter fullscreen mode Exit fullscreen mode

Now let's compile the code:

npm run tsc
Enter fullscreen mode Exit fullscreen mode

and admire the output:

filterByTerm.ts:6:25 - error TS2339: Property 'url' does not exist on type 'object'.
Enter fullscreen mode Exit fullscreen mode

Here we go again! It makes sense, at least in TypeScript: the generic JavaScript object does not have any property named "url". And for me this is where TypeScript really starts to shine.

So what's really the difference between JavaScript and TypeScript? It's not that JavaScript does not have types. JavaScript has types but they are "loose", dynamic. In other words you can change a variable's type later in the code, or assign new properties to (almost) any object.

Now, at first it will look like alien syntax, but once you get accustomed to interfaces you'll start to use them all over the place. But what's an interface by the way? An interface in TypeScript is like a contract. Or put it another way an interface is like a "model" for your entity.

By taking a look at our code we can think of a simple "model" named Link for an object whose shape should conform to the following pattern:

  • it must have a url property of type string

In TypeScript you would define that "model" with an interface, like so (put the following code at the top of filterByTerm.ts:

interface ILink {
  url: string;
}
Enter fullscreen mode Exit fullscreen mode

With the interface declaration we say "I want to use that shape in my TypeScript code from now on". That's not valid JavaScript syntax of course and it will be stripped away during compilation.

TIP: it is a good idea to prefix interfaces with a capital I, that's a convention in TypeScript

Now we can use our interface ILink, which is actually also a custom TypeScript type, by fixing the parameter "input":

function filterByTerm(input: Array<ILink>, searchTerm: string) {
    // omitted
}
Enter fullscreen mode Exit fullscreen mode

With this fix we say to TypeScript "expect an array of ILink" as the input for that function. Here's the complete code:

interface ILink {
  url: string;
}

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);
  });
}

filterByTerm(
  [{ url: "string1" }, { url: "string2" }, { url: "string3" }],
  "java"
);
Enter fullscreen mode Exit fullscreen mode

At this point all the errors should go away and you can run:

npm run tsc
Enter fullscreen mode Exit fullscreen mode

The compilation step will produce a file named filterByTerm.js with plain JavaScript code in the project folder. You can check out the file and see how TypeScript specific declaration are stripped away.

Since "alwaysStrict" is set true the TypeScript compiler also emits "use strict" at the top of filterByTerm.js.

Great job on your first TypeScript code! In the next section we'll explore interfaces a bit more.

TypeScript tutorial for beginners: interfaces and fields

TypeScript interfaces are one of the most powerful construct of the language. Interfaces help in shaping "models" across your application so that any developer can pick that shape and conform to it when writing code.

So far we defined a simple interface, ILink:

interface ILink {
  url: string;
}
Enter fullscreen mode Exit fullscreen mode

If you want to add more fields to the interface it's a matter of declaring them inside the block:

interface ILink {
  description: string;
  id: number;
  url: string;
}
Enter fullscreen mode Exit fullscreen mode

Now any object of type ILink must "implement" the new fields, otherwise you get an error. In fact by compiling the code with:

npm run tsc
Enter fullscreen mode Exit fullscreen mode

TypeScript screams at you:

filterByTerm.ts:17:4 - error TS2739: Type '{ url: string; }' is missing the following properties from type 'ILink': description, id
Enter fullscreen mode Exit fullscreen mode

The problem is with the argument of our function:

filterByTerm(
  [{ url: "string1" }, { url: "string2" }, { url: "string3" }],
  "java"
);
Enter fullscreen mode Exit fullscreen mode

TypeScript is able to deduct by looking at the function declaration that the argument is of type Array of ILink. Thus any object inside that array must have (implement) all the fields defined in the interface ILink.

Most of the time that's far from optimal. After all we don't know if every new object of type ILink will ever have all the fields. Worry not, to make the compilation pass we can declare interface's fields optional with a question mark:

interface ILink {
  description?: string;
  id?: number;
  url: string;
}
Enter fullscreen mode Exit fullscreen mode

Now both the editor and the compiler will be fine. Yet TypeScript interfaces can do a lot more, in the next sections we'll see how to extend them. But first a brief note about variables in TypeScript.

TypeScript tutorial for beginners: typing variables

So far you've seen how to add types to function's parameters:

function filterByTerm(input: Array<ILink>, searchTerm: string) {
    //
}
Enter fullscreen mode Exit fullscreen mode

TypeScript is not limited to that, of course you can also add types to any variable. Let's extract function's arguments one by one, for the sake of illustrating the example. First I'm going to extract every single object:

const obj1: ILink = { url: "string1" };
const obj2: ILink = { url: "string2" };
const obj3: ILink = { url: "string3" };
Enter fullscreen mode Exit fullscreen mode

Notice how I can say to TypeScript that obj1, obj2 and obj3 are of type ILink. In "vanilla" JavaScript you would write:

const obj1 = { url: "string1" };
const obj2 = { url: "string2" };
const obj3 = { url: "string3" };
Enter fullscreen mode Exit fullscreen mode

Next up we can define an array of ILink like so:

const arrOfLinks: Array<ILink> = [obj1, obj2, obj3];
Enter fullscreen mode Exit fullscreen mode

And finally the search term:

const term: string = "java";
Enter fullscreen mode Exit fullscreen mode

Here's the complete code:

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

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);
  });
}

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

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

const term: string = "java";

filterByTerm(arrOfLinks, term);
Enter fullscreen mode Exit fullscreen mode

Ok, I feel you. TypeScript looks more verbose and sometimes redundant compared to JavaScript. But with time you'll see that the more you add types, the more your code becomes robust.

The more you help TypeScript to understand the intent of your code by adding type annotations, the more you'll be fine later. And your developer experience will skyrocket.

For example now that arrOfLinks is associate with the correct type (array of ILink), your editor is able to infer that every object in the array has a property named url, as defined in the interface ILink:

TypeScript interface infer

Now tell me this isn't fantastic because indeed it is. TypeScript has a lot more types besides string, Array and number.

There are booleans, tuples, "any", never, enums. With time you'll learn them all. If you're curious check out the documentation for the basic types.

Now let's move on to extending interfaces.


Stay tuned for part 3!

Top comments (0)