TypeScript Tip – Turn a class into an interface

Transcript:

Hello everybody!

In this video I wanted to share a quick TypeScript tip with you. And that tip is that you can create interfaces based off of classes.

Here in front of me I have some code from a game I’m working on.

Don’t worry about the details just focus on line 410 where we have interface ShapeGenerator extending Generator.

Nothing too exciting there if you’ve already seen my video on interfaces, but if we jump to the definition of Generator… You’ll see that it’s a class and not an interface!

It turns out that TypeScript will let you create interfaces from classes, and it does this in an intuitive way: All the public class fields and methods become part of your interface. And then, of course, you can add more if you need them.

Now, why would you want to create an interface in this way?

Well, for this program in particular, I have a number of classes that are based on Generator, but they are all completely separate in their implementation. However, some of them share these three fields, and I want to be able to do things like create arrays of ShapeGenerators.

Create a new interface based off of the Generator class is a super easy way of making it so that I don’t have to repeat all the fields of Generator, and I don’t have to add any extra levels of inheritance just to be able to treat all my similar classes in similar ways.

You won’t need it all the time, but in cases like this, it’s helpful to know that this feature is there.

That’s it for now.

If you want to dive deeper into TypeScript, I’m working on a course called Learn TypeScript by Example. You can find it at https://typescriptbyexample.com/ If you scroll down to the bottom you can put in your email address and I’ll let you know when the course is released.

Thank you so much for watching! I will see you in the next video.

Learn TypeScript #5, Basic Classes

Transcript:

Hello, everybody! Today I’m going to show you how to use classes in TypeScript. There’s a lot to cover about classes, so for this video we’re going to stick to the basics, and then in future videos we’ll dig into the really advanced stuff.

At their core, classes are a way of describing what data you want certain objects to hold, along with methods that act on that data in various ways. There are a lot of other features built on top of this, but that is the core.

If you come from a language like C# or Java, classes in TypeScript are very similar to what you worked with there. Though there are some important differences in the low-level details, which we’ll be covering in later videos.

Unlike Java or C#, however, there’s nothing in TypeScript that is forcing you to use classes. So if you prefer to write your code in some other way, you are perfectly free to do so. Classes are just another tool you have in your toolbelt as a TypeScript developer.

If you’ve been following the development of JavaScript over the years, you’ll know that the latest JavaScript standards have added support for classes. TypeScript classes build directly on these standards, but with the added benefit that you can use TypeScript to target older versions of JavaScript, and the classes you write will work perfectly well when compiled down to code that’s compatible with older browsers that don’t support classes in JavaScript directly.

So let’s start off with a simple example class.

Let’s say we’re making game and we’re planning on having multiple players and a decent amount of logic common to the players to handle things like movement, displaying the players, and so on. This sounds like a good candidate for a class, so let’s create a class called Player.

Creating a class is very simple, we’ll write the class keyword followed by our class name, and then the contents of the class are contained within curly brackets. If you saw the previous video on interfaces, you’ll notice that class definitions and interface definitions share a lot of parallels, which you’ll see more of going forward.

Let’s pause for a moment and note that this is actually a complete class. It’s empty, so it’s not very useful, but before we start putting things in it, I want to show you how to create an instance of this class.

We’ll create a new variable called “p”, and set it equal to “new Player()”.

The “new” keyword is how you indicate that you’d like a new instance of an object that’s defined by a certain class. Player is obviously the name of your class, and your parentheses indicate that this is a function call.

Now, we haven’t actually defined any function here, so what function is it calling? Well, all classes have what are called “constructor” functions, and if you don’t create one explicitly, TypeScript will create one for you. We’ll go into more detail on this in a bit.

Let’s log the variable and see what it looks like.

You’ll see that Node.js is actually very helpful here, showing us the class name, and then the empty curly brackets will contain the data in our object once we actually start adding some fields to our class.

So, how do we add things to our Player class?

The first and easiest way is to simply write the name of the variable, and then assign to it whatever value you want.

Here we’ll say each player needs and x and a y position, so we’ll set both of those equal to zero.

If we run our program again, you’ll see that I have a player with an x and y of 0. Excellent!

These fields are just like the fields of any other object. For example, I can set “p.x” equal to 1, and that will do what you expect.

A note to those from other programming languages: Fields in TypeScript are all publicly accessible by default.

Just to prove to you that we have an isolated instance of our object, let’s create a second variable called p2, and log that one out as well.

And you’ll see it’s only our original object that has changed, just as we would expect.

Going back to our class definition, note that if you wanted to be explicit about the type that you wanted a field to be, you can indicate that in the usual way by simply writing a colon and then the type that you’d like.

What we’ve done so far works well for fields that will always start off at some default value, but what about a case where there isn’t necessarily a correct default value?

For example, let’s say we wanted to give each Player a name. We’ll add the name field and say that it should be a string.

But, what’s a good default for that? Well, an empty string might not make much sense there. Neither would a random name. Plus then we’d have to remember to change it properly after instantiating it.

Probably what makes most sense would be for us to force the name to be specified at the creation of the object. We mentioned previously that there was a constructor function being created for us, let’s define it ourselves now so that we can use it to require a name.

You can do that by going to the class definition, typing “constructor”, parentheses and then curly brackets for the constructor function body.

We’ll add “name” as a parameter to this function, and we’ll assign “this.name” equal to the “name” parameter.

You’ll see that now TypeScript is giving us errors about the places where we’re creating instances of this class because we’re not providing a name.

Let’s name the first one “Tyler” and the second one “Fred”. And now TypeScript is happy again.

A couple points of clarification on constructor functions. First, the special “this” keyword refers to the particular instance of the class that you’re currently working with. Here in the constructor, that’s the newly created object that we’re building up.

Second, the way these default values for x and y are set by TypeScript is by moving the assignment to the very top of your constructor function.

So assigning a value to them on the same line as their definition is directly equivalent to writing “this.x = 0” at the top of the constructor function.

In general, since assigning the default value where the field is declared is more compact than the alternative, I tend to favor doing that in my own code. If you prefer to leave the declaration as only the types, and initialize all the defaults in the constructor that’s a perfectly valid stylistic choice.

Note that if you’re initializing something in the constructor, the field declaration must have a type on it, because otherwise TypeScript will not be able to infer what type it is that you want.

Okay, so we’ve got some data that we’re holding in our objects, let’s create a function to act on that data.

Let’s say we don’t think the output of console.log is quite to our liking, so we’d like some other way of displaying our Players.

Well, we can create a new method by specifying the name, the arguments in parentheses, and then we can specify the return type with a colon after the parentheses.

And we’ll type out a call to console.log() that formats things a little more nicely.

And we’ll run that, and hey! Things are looking pretty good.

One thing you’ll notice down below is that the list of functions that are part of an object aren’t included in the default console.log() output in Node.js. Generally this is fine, since the data will be more important to see, but this is something you should be aware of.

Let’s tie things into the previous video by showing how we can make interfaces and classes work together.

Since we’re working on a game here, I imagine we’ll have a number of different types of objects that we’ll want to treat generically in some kind of way. Perhaps we’ll use some common fields to do collision detection or to draw them or whatever else.

To define what we need, we can create an interface listing what we’ll need from each game object.

Let’s say this is a 3D game, so we’ll need an x y and z position.

In order to indicate that we want our player to be compatible with this interface, we can use the “implements” keyword followed by the name of the interface. Note that if you had multiple interfaces, you can separate them by commas.

Now we’re getting an error about our player not implementing the interface correctly because it doesn’t have the “z” field, so let’s add that.

And now everything is good.

Down lower in the code, we can create an array of GameObjects, and put our two player objects in that array. Since our Player class properly implements the GameObject interface, TypeScript is perfectly happy with this.

Note that the “implements” keyword on our class definition isn’t actually required as you might be used to from other languages. If we remove it, everything is still fine. The difference here is that if we were to remove our “z” field, we now get an error down in our array rather than on the class itself.

So why would you want to use the implements keyword if you can get away without it?

The biggest reason is that it will tell you while you’re focused on just the class definition itself whether or not the class properly implements the interface.

It also serves as great documentation for your future self and other programmers that the specific class is intentionally conforming to some interface.

A third reason, is that some frameworks like Angular will check objects for specific methods. If they exist, Angular will call them, if they don’t, Angular won’t. Including the “implements” keyword and the relevant interface for that specific Angular feature will cause TypeScript to validate that you are actually including that method properly. If not, you’ll get an error, which is helpful.

Okay, we’ll pause here and in future videos we’ll be covering things like inheritance, private, public, and protected fields, static properties, accessors, the nitty gritty details on the implementation details behind classes, and much more.

If you’ve enjoyed this video and want to go deeper, I’m working on a course on TypeScript. You can find it at https://typescriptbyexample.com I’m still working on it but if you scroll down to the bottom of the page leave your email address there I’ll shoot you an email when that course is released.

That’s it for this video! Thank you so much for watching, I will see you in the next one.

Learn TypeScript #4, Interfaces

Transcript:

Hello, everybody! In today’s video I wanted to show you interfaces, what they are, how to use them, etc. So let’s get started!

What exactly are interfaces? Well, interfaces are a way of ensuring that the objects you’re passing around throughout your program conform to your expectations. You can describe the shape of a paricular object, and the types of all its fields, and TypeScript will enforce that your expectations for that object match reality.

This becomes immensely valuable once your program starts to grow past even a few dozen lines long.

Interfaces are one of the basic building blocks of TypeScript code, and you’ll see them used everywhere. Here I’ve opened a typings file that’s internal to TypeScript itself, and if we search for interfaces we get 86 results just in this file alone! So they’re definitely something you want to be familiar with.

Interfaces pair really well with classes in TypeScript, and we’ll be going into detail on classes in a future video.

Let’s start with some simple examples of an interface. Say we’re writing a web app; we’ll likely have lots of different code that will need to work with user data, so user data seems like a good candidate for an interface.

We’ll create an interface using the interface keyword, followed by the name we want for it. I’ll call this User.

Then we’ll define the fields a user should have. Let’s say we want the name as a string, the time of their first visit as a Date, and their role is going to be either a “admin”, a “manager”, or a “employee”.

That’s a complete interface!

Let’s create a variable that uses this new interface. Like any other variable, we’ll give it name write a colon and then the type, which in this case will be the name of our interface.

We’ll then put in a name, create a new Date object which will hold the current time, and we’ll say the role is “admin”.

So what do interfaces look like when they’re compiled? Well, TypeScript interfaces are “zero cost”, which means when you compile your code they completely go away.

If we open up our compiled code, you’ll see that there’s no trace of our interface to be found.

This means that all the enforcement of the interfaces happens at compile time, and when your program is running, it’s not going to be slowed down by interfaces in any way.

On the other hand, if you’re receiving data from some external source that you’ve defined an interface for, TypeScript won’t be able to tell you if that external source has given you valid data.

So make sure that you write your own validation code in that case to prevent any runtime errors.

If you’ve been following along in the series so far, you’ll have seen me use the “type” keyword to create some new types that were useful for our programs.

The type keyword can describe objects in a similar way that interfaces can, but you generally won’t find it used that way.

Interfaces are the standard way of saying “here’s how I expect this object to look”, whereas the uses of the “type” keyword are more about combining various types in various ways.

You’ll see this more in the video on classes, where we’ll have classes declare that they “implement” certain interfaces, and TypeScript will ensure that they actually do.

In general, if you need to create a type for some specific object, use an interface. Otherwise use the type keyword.

One interesting thing you can do is create interfaces that are based off of other interfaces.

Let’s say we actually want to store some additional data on admin users. What we can do is say “interface AdminUser extends User”.

Currently I haven’t yet listed any fields. But if I were to change my use of type User to type AdminUser, TypeScript will continue to be happy because all the types from User have carried down to AdminUser.

For AdminUsers, let’s add an array of things that they have access to.

You’ll see that I now have this red underline here because the access property is missing. If we give this user access to a few things, TypeScript will be happy again.

An interesting thing interfaces can do is override fields that are defined in interfaces that they’ve extended.

A good example of this would be the “role” field. On AdminUsers, it’s always going to be “admin”, so let’s specify that.

Now if I try to change myself to an employee from an admin, you’ll see that TypeScript is now throwing an error.

Overriding fields like this is pretty useful, and you may see it in TypeScript code you encounter in the wild every now and again.

Another thing interfaces can specify is functions that should be a part of them. Let’s say for our AdminUsers we want some way of logging their name to the console.

You’ll simply write the function name, parentheses, the argument list and then the expected return value of the function.

Since it’s just an interface, we don’t provide any implementation for this function, that’s left up to the actual instances of the object.

We’ll add a function down here to satisfy the interface, and TypeScript is happy with us again.

Note that if we do something like alter the expected return type, TypeScript will helpfully indicate the error so that we know to update the usages of that interface.

Another very helpful thing you can do with interfaces is define optional properties. So let’s define an optional property called “jobTitle”. All that it takes to do that is to add a question mark at the end of the name. And you’ll see that since it’s optional, TypeScript isn’t giving us an error about the jobTitle field being missing from our user object.

Of course, it will give us an error if we define the field with the wrong type.

If you have strictNullChecks turned on, an optional field can only be the type you specify or undefined. It can’t be null. If you need null as a valid value, you’ll need to explicitly spell that out in the type definition.

If you want to prevent people from modifying an object that implements a certain interface, you can describe certain properties as “read only” by prefixing them with the “readonly” keyword.

Let’s create a variant of our User interface that is all readonly called ReadonlyUser. Perhaps we want to make sure that our user interface code is only displaying users, rather than modifying them in any way.

So now if we create a new ReadonlyUser, TypeScript will give us errors if we try to modify any of the fields.

Note that this is a fairly weak guarantee, however. If we create a variable of type User and assign our ReadonlyUser to it, TypeScript will be perfectly happy doing so, because all the necessary fields are present. But now we can modify the ReadonlyUser by way of this plain User variable.

So think of readonly as a light helper or as a bit of documentation to other programmers, rather than a strict guarantee that the field marked as read only will never change.

Moving on: a common mistake that you might make while using an interface is making a typo on one of the field names. TypeScript has a feature called “excess property checks” which aims to help prevent this kind of mistake.

Looking at our AdminUser here, let’s typo the “name” field.

TypeScript is now giving us an error that this unknown field does not exist on type AdminUser, which helpfully let’s us catch our typo.

However, the excess property checks only happen on object literals. So, that’s situations like this where we’re assigning directly to a variable, or if you were to pass an object literal to a function that’s expecting an interface, TypeScript would do these checks as well.

However, if you’re pulling in a variable in some other way, the excess property checks aren’t going to happen.

To show you what I mean, let’s change our variable declaration a little bit, and add a typo on the jobTitle field.

We’ll then create a new variable of type AdminUser and assign our previously created variable to it.

Since excess property checks won’t be used in this situation, TypeScript is only guaranteeing that the minimum required fields are present, which they are. The field with the typo is just ignored as extra data which isn’t important to the interface.

This problem very well illustrates a general principle of how TypeScript interfaces work: TypeScript will guarantee that the object has at least the fields you describe. They might have more fields, they might have extra functions, they might have none of their optional properties, they might have whatever else. But you can be sure that they have at least what you specified.

That’s especially useful to know if you’re going to do something like loop over all the fields in an object. If you do that, TypeScript is only guaranteeing that you’ll see the minimum required fields, but you might see more things depending on where your data came from, so you’ll need to make sure your code will handle that properly.

Generally you won’t run into these kinds of issues on a day-to-day basis, but it’s really helpful to be aware of them for when you eventually do.

Moving on, let’s say you have an object that you’re using to store other objects. For example, we might want to have a mapping between a user’s ID and the user data itself. You can use what are called “index properties” to describe this sort of situation.

Let’s create a MapOfUsers, and we’ll associate a string ID to the user data.

You’ll see here in square brackets we define the type we’ll be accessing with, and then after a colon outside the square brackets, we list the type we’ll return with that access.

Let’s use this new MapOfUsers type. We can put in our existing user, and that’ll work just fine.

We could also define a new user in-line, and TypeScript will enforce that it has the proper values.

TypeScript will also enforce that you use the proper index type. Here we used strings, but we could also switch to numbers instead.

You’ll see that after making that change, TypeScript indicated an error until we switched all of our values from strings to numbers.

The final feature of interfaces I wanted to show you is the ability to modify previously declared interfaces. I don’t see this used very often, but it’s good to know that it’s something that exists.

In order to do this, you simply write out the interface keyword and the name of the interface you’re going to modify. Let’s let’s pretend that this interface user is defined in some file A and this other one is defined in some file B. Perhaps in file B we’re asking the user for their age and we want to indicate that as part of the user document.

You’ll see now down here on the user we’ve created, we’re now getting an error about age not existing. So as simple as that we added a new field to the interface. Perhaps we should mark it as optional.

You’ll want to be really careful when you do this, because adding new required properties could easily break your existing code. Your code will also become harder to follow because now your interface definition is split across multiple places, so it’s hard to get the big picture view of what the whole thing looks like.

The main use case for this feature is if you are modifying a library that you’re using, or if you’re monkey patching one of the built in JavaScript objects. Otherwise I reccomend against you using this feature.

Alright! That’s it for interfaces! Stay tuned for the upcoming video on classes, which interact with interfaces quite a bit.

If you’ve enjoyed this video and want to go deeper, I’m working on course about TypeScript. You can find it at https://typescriptbyexample.com Scroll down to the bottom and put in your email address, and I’ll let you know when the course is released.

Thank you so much for watching! I’ll see you in the next video.