TypeScript: From First Steps to Professional

Type Annotations Exercise

Anjana Vakil
Software Engineer & Educator
TypeScript: From First Steps to Professional

Lesson Description

The "Type Annotations Exercise" Lesson is part of the full, TypeScript: From First Steps to Professional course featured in this preview video. Here's what you'd learn in this lesson:

Anjana instructs students practice adding type annotations to variables, function parameters, and return types in a TypeScript file. She also walks through a possible solution including handling undefined values, using type guards, and leveraging TypeScript's type inference capabilities.

Preview
Close

Transcript from the "Type Annotations Exercise" Lesson

[00:00:00]
>> Anjana Vakil: Give us the types OK, no problem Only I'm not going to give you the types, you're going to give TypeScript the types So at this point, we know how to add type annotations to variables, function parameters, function return types, and we know how to create new types of type aliases So we're going to exercise our typing muscles, I guess, literally and metaphorically, to add type annotations everywhere we can in this file

[00:00:35]
And so just so you have as a reference, this is like a simple little example that kind of combines a bunch of the syntax that we've talked about Just to, just an illustration of syntax So we can declare a type alias, type, type name Again, convention is to use capitals, like a class Equals type and here's where we can use stuff like array types, and then we can type variables with that colon

[00:01:07]
We can type function parameters, function return types, and we can reuse these alias types as many times as we want, almost as if it's like a variable for the type So this is just a syntax refresher So what we're going to do is add as many types as we can to type me.js and there are plenty of ways you could do this

[00:01:28]
This is where we're starting to get a little bit into the creative side of typing our scripts So that is your mission, should you choose to accept it And the goal is to get this code fixed So the types that you use hopefully will help fix a lot of the problems in this code, only one of which we have been helpfully told about by Node.js so far

[00:01:58]
And there might be other problems that may or may not be fixed by types So it's up to you to fix this to the best of your ability How is everybody feeling with this Is this, we're starting to get into the vast open world of creating and using TypeScript types So if you feel a little overwhelmed, that's normal And we're all in good company here

[00:02:33]
So let's talk through it together With this file, I've added some data here so that you can get a sense of like what kind of data these functions and this script is intending to work with And I've added some comments to help nudge things in the right direction, but there's an infinity of different ways to type a TypeScript file, so our mileage may vary, and where and how you kind of started adding types here might have been at the top, work your way down, might have been at the error here that TSX was giving us

[00:03:23]
Would anybody like to walk me through one change that they made to this piece of code Yeah Creating a type that's just event and then defining that Totally OK, so can you walk me through how we did that and where you did that maybe So I did it up at the top above the events array Totally legit and a common thing that you'll see is a lot of like type aliases and type definitions at the top of a TypeScript file

[00:03:51]
Could you walk me through the code you wrote Yeah, so I've just got type event equals, and then an object I've got ID is number, title string, date string, image URL with the question mark at the end since it's for a string Same thing with the description where we've got the question mark, and then I made host ID a number, and then RSVPs I made optional, and then defined those as an object, right, which I don't know if that is really the best way to do that, but OK, so can you walk me through what you wrote after the, do I have this right so far

[00:04:29]
Sorry, I was trying to No, I think that's all right Yeah And then I had, I just put object and then a right like the object, array, yeah, OK And then I for the events after events I did the colon and then events array, so it knows it's right here, yeah Oops, not J colon And then the name of the type which I assumed you meant capital event

[00:04:57]
Yes, yeah, yeah, yeah Awesome So event and then All right, yeah, yeah OK OK, great No, it's angry at you're right though Now it's angry at this ID Could anybody help me fix it being angry at this ID It says type string is not assignable to type number Couple of ways we could fix this Making a number Wait, yeah, sorry

[00:05:24]
Dave in the chat actually forced it to be an ID or forced it to be a number, but then another option is to use the union string and number Yep So one thing we could do is like change the data and make it a number, but to exercise our typing muscles, we can change this ID to be the union of string and number Was that it, Dave

[00:05:52]
Cool Like that Awesome And once we save that and do that, now we have ID is string or number, so it's happy with that one and it's happy with this one And it looks like we're also getting a complaint about this host ID, very similar So I think maybe we can apply the same strategy Does that sound good So on our host ID, we can write it in the other direction

[00:06:21]
Do y'all think there's a difference between string or number and number or string I'm seeing a shake no, and that is a yes So, yeah, no, there's no difference in terms of how TypeScript sees a union type If you think about sets, like when you add stuff to a set, it doesn't matter what order you add it in So it's kind of like that

[00:06:46]
OK, cool We're making some changes, we're getting some places Now we're still getting this event RSVPs length problem Anybody have an idea how we can use what we've done so far to help get rid of that pesky properties of undefined error There's actually another way that we can tell TypeScript that this event thing that we're getting in as the input to our function is going to have an RSVPs property and that property is going to be the kind of thing that has a length property

[00:07:28]
Arrays are the kind of thing that have a length property And we have an array-like situation here in our RSVPs So could we use a type annotation to give TypeScript a little more information here Like a type of RSVP OK, yeah, we could also, we can flesh this out like not just any object, but it could be a type RSVP And we can make that something that looks like some of the data that we have, for example

[00:08:14]
So it seems like it just has a user ID And the user ID in this data is type Number Number or string, so union type, yeah, sounds good OK, number or string And then we can use that in our event type We can use this alias instead of just like the huge wide type of all possible objects ever We're going to say no, no, this is a more specific object

[00:08:53]
It's going to be an array of RSVP objects Cool And so we still have it as optional here because one of the elements in our data set here didn't have an RSVPs array So sounds like we've already, there's one way to fix that, which is to make it not an optional property and change the data to make sure that it has an empty array or something like that

[00:09:22]
But let's see if we can use some of the type guards that we were talking about earlier So now that I have this event type, can I start adding some information to some of these type signatures Perhaps by saying that the event argument coming in better be of type capital event For example So now I'm noticing something in my editor

[00:10:06]
Are you noticing something that's a little bit different, which is now instead of just getting no information in the editor and our good old fabulous, cannot read properties of undefined, I'm actually getting a hint in my IDE And that's because my IDE VS Code knows how to use TypeScript to help me look at my code as I'm writing it

[00:10:35]
And so now it's giving me perhaps an even more helpful message saying, hey, don't forget, since you made that RSVPs optional, it might be undefined And so here's where we could do, we can still do our ternary Event RSVPs, event RSVPs length or zero, we could do something like that We can use kind of like nullification, null nullishness to our advantage here, and this is sort of, I mean, I'm guessing that you're thinking something like instead of RSVPs dot length, we want to make sure that if there is no RSVPs, we default to something like 0

[00:11:38]
OK, so we could do, for example, a question mark right here Will, instead of trying to read the dot length property of undefined, it will just give us the value undefined and not create that error where it's trying to read the length However, then we would have, OK, so I saved that, and that question mark did actually make the cannot read properties of undefined error go away, but now in our tests we're getting some undefined, unsurprisingly

[00:12:23]
So, for example, we are not getting the strings that we expect to get out of these various functions So if we want to make sure that this does not end up undefined and instead gives us a number in the case where it is undefined, it's essentially another roundabout way of writing a similar if is I believe where you were going with this is we can do the or operator, logical or and a fallback value, essentially

[00:12:57]
Now, this is taking advantage of truthiness and falsiness in JavaScript, which means we are opening up Pandora's box, but one of the cool things about JavaScript is because it's flexible like this, once we know how that works, we can leverage it to our advantage So this is another way that we could put a type guard in here, and basically make sure that the case where RSVPs is undefined is handled

[00:13:27]
But this is very similar, just a different way of approaching this This is essentially the same functionality as if we were to do it with that ternary that we had before So OK, cool All right So we've got that one error not happening Awesome But we still don't have our tests passing So let's go through and try to add more types

[00:14:02]
Did folks add something like an event type to the other function signatures that take in an event We can do that, perhaps So since that's named event, I'm assuming it's meant to be an event And then we have some other stuff OK So adding just these event types didn't get us all the way there We're still getting weird results in our tests

[00:14:38]
And we don't have any type errors to help us figure out what they might be So let's try to understand what's happening here We are calling get event details one, and we're expecting to get this long string, and instead we're getting undefined So, for example, this given RSVP count is meant to return a string And we can annotate a return value on a function, regardless of whether it's an arrow function or a function keyword function, by putting a colon after the parens where we declare the parameters

[00:15:23]
And then the type of the return type that we want, which in this case, we could make it a string For example Now let's see what we've got I mean, that didn't change anything about the functionality We're just starting to work our way through an informationless file, adding information to it Where else could we add a return type that might be useful

[00:15:54]
Well, OK, here I just said this should return a string and we can also see in the tests that we're expecting strings, so I'll just do the same thing here Ha ha, now we're getting a squiggle And this is starting to help us figure out what's happening here Let's try to understand what TypeScript is trying to tell us OK, so this get event details call, as we can see in two of these tests, we're getting back, the actual result is undefined

[00:16:44]
So somehow this function get event details is returning undefined And can anybody guess why that might be happening Looking at this code What's the, what is the situation in which this might return undefined This is just a JavaScript question If event is not defined If event is not defined, or event is falsy, basically, in any way

[00:17:21]
If it's null, if it's zero, if it's whatever, then this if event case is not going to happen And then what happens after that Nothing, which in JavaScript in a function with no return means return undefined And that's what this is sort of starting to encourage us to do is to say, hey, look, you need to be more explicit

[00:17:55]
Either you were expecting this function to maybe return undefined, in which case, you need to include undefined in the return type So that I TypeScript know that undefined is a possible and valid return value for this function However, since you didn't even put a return, I TypeScript, I'm not sure you know what you're doing, programmer

[00:18:28]
So figure out whether you really want this to be returning undefined, in which case add it to my return type, so I know that, like, for example, with a union OK That satisfied it And yet we know from the tests that that is not the behavior we want We don't want this function to return undefined We want it to return a string

[00:19:06]
That's what this very helpful comment says, that we should return a string no matter what If we don't find anything, we should return the string event not found So we don't TypeScript We don't want you to let me return undefined from this function Instead, we're going to take the other side of this error and say, hey, we need a return statement that returns the right kind of value

[00:19:41]
Does anybody have a sense of how we can make sure that this always returns a string and that in the case where there is no event found that we return an event not found And else statement or just after that if you just write return Sure, yeah, we could put it in else or we know that if the if doesn't match, it's just going to drop through so we can save ourselves a few keystrokes and write return event not found

[00:20:20]
And now well, one of the tests started passing because we looked for this event ID that doesn't exist 404, and sure enough, we got the string back that we expected Now, TypeScript is also no longer complaining to us, hey, this isn't always returning a string, because in this case, it will always return a string, and TypeScript knows that because it sees that there's a return string here

[00:20:49]
It's a template string that I'm populating with some other values, and there's a return if that other case doesn't match And it so happened that the string was what our tests expected, so cool We're chipping away at these problems Now, how about if we, let's say, were to make this a string in this call That still works

[00:21:31]
But maybe we don't want it to work Maybe we want to make sure that whatever ID is being passed into this function is a number How could I do that We could type it as a number So we could do that inside of the function definition, when we're declaring this parameter of an ID, can add a colon and say it's supposed to be a number

[00:22:04]
Now, is that right Does that feel right Let's go here Would it be ID So we can return or we can give it a more, a more helpful type that matches our data better So we can give it a type like ID So how could we, how can we make the type ID Well, similar to our RSVPs here, we can take the type of the ID thing that we had already thought about when we were declaring our event type and essentially name it a new type alias ID

[00:22:42]
For example And now we can use that Basically just added a little bit of indirection, but so far we haven't changed anything about the event type We've just added this other helpful ID type, which then we could use to, sorry, wasn't there Here we can use instead of our number Which now if we were to do this Yeah, squiggle

[00:23:19]
No, you said it was going to be a number But so if we want to be able to accept whatever types the ID type accepts, we can use the type alias instead of writing out the type manually And so now, it's OK if we have a string coming in At least in this test case, it's OK But maybe we want to take a look at this get event by ID function and see more closely what it's doing

[00:24:18]
So here in my get event by ID, we can maybe use our new ID type What do you think OK, so I'm going to type this parameter ID as an ID And then what about the return type of this function According to this comment, and we're just going to try to be super explicit on all of these functions and make sure we're typing our return values, we're typing our parameters

[00:24:52]
I want to make sure that this returns either an event object or null How could I write that and make sure that TypeScript knows that is the return type I want from this function Just be event or null OK, yep, so I can after these parens do colon and then event And, or I'm guessing you mean the pipe operator, yeah, I mean, we'd say it as or in language and null

[00:25:27]
So I can use these union types I can use type aliases I can use both wherever I can put a type Let's see, let's look at this get event date and if we want to understand what's going on with this test that's not passing, we're probably going to need to kind of trace it through, yeah OK So I call get event details with the number one

[00:26:05]
Go to get event details Here we go, and that's going to call get event by ID, it's going to pass in the ID And that's going to go to get event by ID and it's going to compare the value of that ID to each of the ID properties in the objects in our events array, and filter out any that don't match Now, why are we getting event not found

[00:26:51]
Because this guy right here is forcing JavaScript not to do its magic of coercing types from numbers to strings And if we look at our data, our ID one, it's actually a string So since we have this triple equals, the string one and the number one are not the same And the reason why we want the triple equals most of the time is because we don't want type coercion in JavaScript

[00:27:23]
So now we face a dilemma And this is the kind of thing that I think you will find in your TypeScript journeys or perhaps you've already found becomes kind of a moral quandary Do I do some fancy type gymnastics to make sure that everything works and checks out, or do I use much more strict and predictable types and make sure that my data checks out

[00:28:01]
And so this is where I think earlier we talked about having this ID actually not allow strings And this is one thing that's nice about aliases If I were to, let's say, change the type that an ID is Now everywhere where I'm using that ID type, like for example, in this initial events array, it knows that each event has an ID property that's supposed to be a number

[00:28:41]
And so it's complaining, hey, hey, hey, hey, hey, you've got something unexpected in your data here So we could make it a number and make TypeScript a little bit happier Although now we're getting problems with like this thing that we allowed before Or turn them both into strings Can we turn them both into strings, for example

[00:29:08]
Let's try that How can I make sure that both the event ID and the ID are strings Or was that we could check if it's, check if it's a number, and if it's not, then stringify OK, so like maybe make this a bit more, give me a bit more space here to do this Something like this, so I could say something like if, how could I check the type of something

[00:29:44]
Hint, type of operator So if type of ID or, or event, I don't know which one we wanted to start with, but ID, type of ID I can actually do this up here, can't I If type of ID is the string number because that's how the type of operator works It returns a string with the name of the JavaScript type, of which there's only a handful

[00:30:29]
So we could do that, we could do that and then do like convert stuff So I could convert it to a string by let's say using the string class or to string, yep ID is ID to string, for example, ah My, now, oh, right, because I Yep, here we go, OK But now if we look at our tests, we maybe did this the wrong direction or not enough or what have you, so we could do that regardless

[00:31:09]
Maybe And just always make sure it's a string Like if we make a string into a string, no sweat If we make a number into a string, then we know that we're always dealing with strings So perhaps we could do like event ID to string equals ID to string Or something like that Or we could use the string, capital string to make string objects out of them

[00:31:47]
OK, so because I had added those curly braces, good catch I'm not actually returning this comparison So if I look at this, I'm going to split this out onto another line I'm just going to say compare is this function and pass it into here, compare And the reason I'm doing that is just so that we can look at the types on this compare, and it doesn't have any

[00:32:19]
So I might have caught that error if I had a return type on here And the thing is with these little anonymous functions, it's like now I have to add like parens and like say, OK, it's going to be a boolean, and then now it's like, no, no, hold on, you didn't return anything and so now maybe I get a hint OK, I should return, but we can also do the sensible thing and undo the syntax change that I did before and make this a, what do you call it, direct arrow function

[00:33:07]
Put it as this whole thing becomes the return value So now, and I'm just going to do this compare trick again, just to look at the type info Now, I still don't really know what I'm expecting it to be But I know that whatever this function returns, it's going to be a boolean And so it's not always going to be falsy forever because it's undefined all the time

[00:33:53]
And oh, JavaScript, thank you for not making any distinction between returning undefined or null or false or zero, or any of these other things that are all falsy types But ultimately we are slowly making progress here Oh my goodness We finally got there Now if I look here at what type event has It actually has a type

[00:34:22]
When it was split out as compare, it didn't have a type It said any, which we're going to talk about later, which spoiler alert means anything, could be anything But here, it knows that it's an event, even though I didn't say something like event in parens I didn't do that, but somehow TypeScript still knows that this is going to be an event

[00:34:59]
And we're just starting to get a little sneak preview here of how TypeScript infers the types in my program, by looking at where this code is, how it's being run And how does TypeScript know that whatever this event gets is going to be an event Any ideas It's inheriting it from the event, right From event The event that we're passing in, well, we're not actually passing it in so much as filter, we're calling filter

[00:35:36]
And what the filter method does is it passes each element of the array to the filter function you give it And what's this array got inside of it as elements Event objects Because we said so And so this is just a little sneak preview of, we sometimes don't need to annotate everything And in fact, in this exercise, I've made you over annotate just because I can

[00:36:03]
But TypeScript can actually do a lot of legwork of figuring out which types we're working with through our program, even without me adding type annotations to every single variable and because, for example, it knows stuff about this filter method and this is a lot of where, what is this syntax, there's angle brackets, there's overload, there's blah

[00:00:00]
We're going to talk about that a bit later But suffice it to say, it knows what the array filter method looks like, how it is typed We're going to talk shortly about how it knows that But it can use that information to help us out, even without us having to manually annotate everything So sneak preview OK, our tests are passing

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now