What do you want to learn?
Leverged
jhuang@tampa.cgsinc.com
Skip to main content
Pluralsight uses cookies.Learn more about your privacy
Getting Started with TypeScript
by Brice Wilson
TypeScript is a powerful, fun, and popular programming language used for building browser and NodeJS applications. This course will teach you all of the most important features of TypeScript, and quickly make you productive with the language.
Start CourseBookmarkAdd to Channel
Table of contents
Description
Transcript
Exercise files
Discussion
Learning Check
Recommended
Course Overview
Course Overview
Hey everybody. My name is Brice Wilson and welcome to my course, Getting Started with Typescript. I'm a server and client-side web developer. Typescript's popularity is exploding and for lots of good reasons. It's a super set of JavaScript that helps you write better code faster and it compiles to JavaScript that will run in Node.js or any modern web browser. In this course, we are going to cover all of the basic typescript topics to quickly make you productive with the language. Some of the major topics that we will cover include; Configuring a TypeScript project, using types with functions, Classes and interfaces, modules, and type declaration files. By the end of this course, you'll know how to use all of the most important features of the language and be ready to begin your first TypeScript project. Before beginning this course, You should be familiar in the basics in JavaScript, but you certainly don't need to be an expert. I'll hope you'll join me on this journey to learn TypeScript with the Getting Started with Typescript course at Pluralsight.
Installing TypeScript and Configuring a Project
Introduction
Hi. This is Brice Wilson. Welcome to Getting Started with TypeScript. In this course I'll teach you everything you need to know to quickly be productive in this modern and mature language. I love TypeScript. It compiles to standard JavaScript and can be used to build client-side browser applications, server-side node applications, as well as any other application that you could otherwise build using JavaScript. The strong typing support, along with the many other advanced features in TypeScript help you build applications with fewer errors that are also more maintainable and easier to refactor. In this first module of the course I'll quickly give you an overview of the entire course and the topics I'll be covering. I'll also show you the MultiMath demo app I'll be using. It's a very simple math game for kids that we'll build throughout the course. After that quick overview we'll dive in and get started building the app. I'll show you how to install TypeScript using npm and how to run the compiler from the command line. I'll also show you how to configure a new TypeScript project with a special file named tsconfig.json. There's lots of important topics to cover, so let's get started.
Course Overview
I just told you what I'll cover in this module, but let me quickly tell you what I'll cover in the remaining modules in the course. In the next module I'll cover all of the basic data types available in TypeScript. In the demos for that module I think you'll immediately see the value of strong type checking. The next module will focus on functions. I'll show you how to use types with functions and how to use arrow functions. Next, I'll show you how to use classes and interfaces to create your own types in TypeScript. This is a tremendously powerful feature that lets you use many object-oriented techniques in your code. I'll then move on to an explanation of modules. They let you write your code in smaller, reusable chunks that you can pull into different parts of your application as you need them. In the last course module I'll show you how you can use type declaration files to add strong typing support to just about any popular JavaScript library you might want to use in your app. Let's now go see how to install and run the compiler.
Demo: Installing TypeScript and Running the Compiler
In this demo I'll show you the very basic version of the app that I'll start with and show you how to install TypeScript with npm, and run the compiler from the command line. There are actually several ways to install TypeScript. A great way to get started is to just visit the official TypeScript website. You can see the address here in my browser, typescriptlang.org. I'll click on the Download link at the top of the page. There are lots of links here to help you get started with many popular editors. If you're a Visual Studio user you can use the links here in the middle of the page to add the latest version of TypeScript to Visual Studio. There is also a link here to Visual Studio Code. It comes with a recent version of TypeScript, but I'll show you how to configure it to use a specific version shortly. Lots of other editors also have great support for TypeScript. There are links for a few of them here on the right side of the page. Follow those to learn more about how to use them for TypeScript development. In this course I'm going to install TypeScript using npm. npm is the Node Package Manager and gets installed with NodeJS. A quick sample of installing TypeScript with npm and running the command line compiler are shown here on the left side of the page. In order to install TypeScript this way you'll need to install Node on your machine. I've got the Node website open here in another browser tab. The address is nodejs.org. Node and npm are practically required for modern web development, so if you don't already have them installed this would be a great time to do it. I'll now jump over to Visual Studio Code and show you the files I'll be starting with. I only have a handful of very basic files in the project right now. The .vscode folder contains settings for my editor. The app folder is where I'll put all of my TypeScript code. There is one file in there now named app.ts. It's currently empty, but we'll add some code to it in just a minute. The CSS folder contains one CSS file from a bootstrap template I downloaded. The node_modules folder will contain any packages I install with npm. I've also got a fav icon, a single index.html file with the markup for the app, and a package.json file that stores information about the npm packages I'll be using. Let's take a quick look at the index.html file. I'll close the project explorer, just to make a little more room. This is a pretty simple file, it's just basic html that applies a few styles defined in the CSS file I'm referencing here at the top. I'll scroll down to the bottom of the page, and you can see I've added a placeholder for references to the JavaScript files that will provide some functionality for the app. I don't have any in there yet, but this is where I'll add references to the JavaScript files that are output by the TypeScript compiler. I also want to point out the div element right above that. I've given it the id messages, and I'm going to use that id to print a message to the screen right after I install TypeScript. The last thing I want to show you before I do that is the contents of the package.json file. There are really two things I want to point out in this file. The first is the one div dependency I've specified. I'm going to use an npm package named http-server to serve up the very simple app we're going to build. It's just a very basic web server that's great for development work. I'll start that web server using an npm script that's also defined in this file. I've defined a script named start that will just run the command http-server. Okay, that's what the project looks like right now. It's now time to install TypeScript and write some code. I'm going to jump over to a terminal window and run npm from the command line. I'm on Windows, but you can do this from a terminal on a Mac or a Linux as well. I'm already in the root directory for my project. I'm going to install TypeScript globally, so I can use it from anywhere on my machine, so I just type npm install -g TypeScript. That only takes a couple of seconds to run, and you can see that it installed TypeScript version 2.2.1. I'll now jump back over to Visual Studio code. Two dot two dot one is actually a newer version of TypeScript than that which came with the version of Visual Studio code I'm using; however, I can configure code to use this newer version by adding one line to my user settings file. The Settings Editor takes up a lot of space, so I'll try to make it fit a little better. These are some of my user settings. The first one in the file is for the typescript.tsdk setting. This setting tells Visual Studio Code where it can find the version of the TypeScript compiler you'd like to use. I've already set it to the location on my Windows machine where npm just put my new TypeScript package. In order for that to take effect, and recognize the version of TypeScript I just installed, I'm going to reload the window. I'll now open my app.ts file, which is currently empty, and you can see in the bottom of the Editor window that Visual Studio Code is now using TypeScript version 2.2.1. Okay, I'll now write a bit of code. I'll start with a basic function I'll name startGame. I'm going to use a code snippet inside the body of the function, just so you don't have to watch me type so much. These are two very simple lines of code that just get a reference to the message div I showed you earlier inside the index.html file, and then assign a string to its innerText property, so that it will appear on the screen. This is actually play old JavaScript code. There's nothing TypeScript specific about it, but that's an important point. Valid JavaScript is also valid TypeScript. So that we have a way to execute this function, I'm going to use another code snippet to call the startGame function when the Start Game button is clicked in the app. This is still just plain old JavaScript, but let's now run it through the TypeScript compiler. I'll go back to my terminal to do that. Since my code is inside the app folder, I'll change to that directory, and then compile the code with the command tsc space app.ts. It ran with no errors. Back in my editor you can see that I now have a second file in the app folder named app.js. I'll open it just below app.ts, so you can see the differences. They're identical, except for the blank line I included on line six of the TypeScript file. It looks like the compiler removed that. I wanted to show you this to make it clear that JavaScript is valid TypeScript, and that the JavaScript output by the compiler is generally very readable. Before I can use the new JavaScript file in the app I need to add a reference to it in my index.html file. I'll open it back up and add a script tag just below the placeholder I showed you earlier. Okay, the last thing I need to do is start the web server. I'll go back to my terminal and run the npm script I defined in my package.json file. I just type npm space start. That starts the server and lets me know that it's running locally on port 8080. I'll go back to my browser and access it by typing, localhost:8080 in the address bar. There it is. This is the user interface laid out in the index.html file. I wired the startGame function to the Start Game button, so I'll click it, and we see that the message I included appears at the bottom of the page. Throughout the course I'll turn this into a fully functional, although very simple, multiplication math game. The input boxes above will let users specify the factor they want to practice and the number of problems they want. A game board will then be printed out where they can provide answers before clicking the calculate score button to see how they did. Okay, now that we've got TypeScript installed and working with the app let's take a look at how to configure a TypeScript project file.
Using Project Files
TypeScript project files are really just simple JSON text files with the name, tsconfig.json. Because they're JSON they're pretty easy to read and edit by hand. They store several useful things about how your project should be built, but the most common things you'll see in them are the compiler options to apply when you run the compiler. It can be a huge time saver not having to type lots of options on the command line every time you compile. Tsconfig files also specify the files that should be included or excluded when you run the compiler. In a very large project you can choose to use multiple project files, each specifying different compiler options for a subset of your project. Being able to include or exclude groups of files in that scenario is very important. Another nice feature offered by project files is that they support configuration inheritance. This means that in a large project you could create a project file at the root of your project that includes all of the default options you want applied to the project. You could then create additional project files in different folders inside the project that can selectively override or add to the options specified in the root project file. I'll show you how to set that up in the next demo. Let's now take a look at a very small tsconfig.json file. JSON files are really just a collection of key value pairs. The key is a string, and the value can be something as simple as a number, but it can also be an object with several properties or even an array containing multiple objects. Here's a very simple tsconfig.json file configured with a few basic compiler options. The first key in this file is compilerOptions. The TypeScript compiler expects to receive an object for that key, and each property on the object should be a different compiler option. Objects in JSON files are surrounded by curly braces. Inside these curly braces you can see that I've specified the target, noUnusedLocals, and outFile options. The value for each option is placed after the option name and separated from it with a colon. Target tells the compiler which version of JavaScript to output. ES5 is supported just about everywhere, so I'm going to use it. NoUnusedLocals will generate an error if I have unused local variables, and the outFile option specifies the name of the JavaScript file the compiler will output. If I needed to apply additional options I could just keep adding properties to the object assigned to the compilerOptions key. There are several other root level keys you can use in a tsconfig file. Here I'm also using the files key. The value assigned to the files key is an array of the files you want included in the compilation. Arrays are specified by providing a comma separated list of values inside square brackets. In this example I only want to compile one file, so app.ts is the only value in the array. Let's now go back to the demo app and configure a new project file with several compiler options.
Demo: Configuring Compiler Options in tsconfig.json
In this demo I'm going to show you how to use compiler options and add them to a tsconfig.json project file. I'm going to start here in my terminal to show you how a couple of compiler options work, and create my tsconfig.json file. You can easily see what version of TypeScript you're using with the --version option. I'm currently using 2.2.1. The help option will print to the screen all of the compiler options. I'll change this to use the shorthand version with just -h. I've got my terminal font set pretty high, so it's not terribly readable on my machine right now, but this can be a handy way to quickly find an option and what it does. You can also find a list of all of the compiler options on the TypeScript website. I'll clear the screen just to clean things up. I now want to create my tsconfig file. You can manually create the file if you like, but you can also use the init compiler option to create a default one for you. I'll just type tsc --init, and you see I get a message that a new project file was successfully created. I'll go back over to my editor, and the new file appears in my project explorer. It sets a few options to get you started, but I want to go ahead and tweak a few of those. I'm going to remove the module option for now. We're going to discuss modules in detail later in the course. It's currently compiling to the es5 version of JavaScript, which is the version most widely supported in browsers today. I'll leave that alone, and the noImplicitAny option is fine the way it is for now too. However, I'll change the value of the sourceMap option to true. That will be a good way to know that the compiler is using the options in this file, and I'll also show you a little later how you can use sourceMaps to debug your TypeScript while the output JavaScript is running in your browser. I'm going to add the watch option, and set its value to true. This is a really nice option that effectively leaves the compiler running. Whenever a TypeScript file on your project is saved the compiler will automatically compile it for you. This can greatly enhance your coding workflow. I like to keep the output JavaScript separate from my source TypeScript, so I'm also going to add the outDir option, and pass it the value js. This tells the compiler to put all of the output JavaScript files into a folder named js. The folder will be created if it doesn't already exist. Since I'm now going to store the JavaScript files in a separate folder, I'll delete the app.js file I created earlier in the app folder. The last thing I want to add to the project file right now is the files key that's used to specify exactly which files should be compiled. The value you specify for it is just an array of strings that are the files to be compiled. Right now I only want to compile the app.ts file inside the app directory. I'll go back to my terminal, and then change into the app directory inside the project. I want to do that just to show you that you don't have to run the compiler from the same directory that contains your tsconfig.json file. The compiler will look for a configuration file in the current directory, but if it doesn't find one it will move up the directory hierarchy looking for one. Since I'm now using a project file, I don't need to pass anything to the compiler. I can simply type tsc and press Enter. Because I added the watch option to the tsconfig.json file you can see the compiler is now running in watch mode. It compiled the code as it is, and is waiting for more changes to be saved. Back in my editor you can see that I now have a folder named js that the compiler created to store the output JavaScript files. I'll open the TypeScript source file, so we can again compare it to the JavaScript output. Since I used the sourceMap compiler option, the output folder contains the app.js file, as well as a file named app.js.map. I'll again open the JavaScript file below the source TypeScript file. They're again nearly identical, but this time you can see a reference to the sourceMap file in the JavaScript. Remember that the compiler is currently running in watch mode. I also have Visual Studio Code configured to auto save my files. Watch what happens in the JavaScript file on the bottom as I start typing new code in the TypeScript file up top. I'll just add a console.log statement. You can see that the compiler is automatically picking up those changes and compiling them in real time. That also means I could see these changes in a browser simply by refreshing the page, since the JavaScript has already been updated. If I jump back to my terminal you can see the results of the compilations that were run as I type the new code. Since I changed the location of the output JavaScript file, I need to update my script reference in the index.html file. I'll change the path to look for the file in the js directory instead of the app directory. I can now go back to my browser and click the reload button to see the changes. I want to quickly show you how the sourceMap files enable you to debug TypeScript code in your browser. I'll press F12 to open the Chrome Developer Tools. I'm on the Sources tab, and you can see all of the files on the left side of the window. Inside the app folder is my app.ts file. I'll open it and add a breakpoint on line four. Keep in mind I just set a breakpoint in the TypeScript file, but the browser is actually executing the code in the JavaScript file that's inside the js folder. I'll now click Start Game. The debugger immediately stops on my breakpoint. At this point, I could use the debugging tools in Chrome to inspect various things and investigate any problems I'm having with my code. I'll click the Continue button to allow the code to finish running. I'll now open the Console tab, and you can see the output from the new console.log statement I added. Okay, we're making progress. We've got a new project configuration file to store our compiler options, and we can debug our TypeScript code in a browser. In the next demo I'll show you how you can use multiple configuration files to more precisely control how files in a particular directory are compiled.
Demo: Configuration Inheritance and Glob Support in tsconfig.json
In this demo I'll show you how one configuration file can inherit settings from another one, as well as how to use globs to specify which files should be compiled. In larger projects it's often helpful to use multiple tsconfig files, one for project wide settings, and one or more additional files for specific sections of the code base. To implement that in the MultiMath project I'm going to edit the config file that currently exists in the root of the project. I'm going to remove the files section, so that I can specify the files to be compiled in the inherited file. I want the compiler options I've already specified here to apply to the inherited files, so I'll leave them as they are. Just to make it clear that this is my base configuration file, I'll rename it tsconfig.base.json. I'm going to create the new inherited config file manually in the app folder. I'll name it tsconfig.json. I'll add the opening and closing curly braces that must surround the options. I'll press Ctrl+space in Visual Studio Code to see a list of the settings I can specify. The first one I need to use is the extends setting. It's used to specify which config file this one extends. This file will extend the base file that sits one directory higher in the project structure, so the value I'll give it is ../tsconfig.base. I don't need to include the JSON file extension. This config file will now use all of the options in that base file, plus any additional options I add here. I'll add compilerOptions section, and set the removeComments option to true. That will just give us a simple change we can observe to make sure it's using both config files correctly. I'm now going to use a glob to specify which file should be compiled. Earlier I used the files key and specified a specific file to compile. A glob lets you specify a file named pattern for the compiler to match. I'll add it to the include section of the config file. It takes an array of the patterns it should match. I'll add a dot, so that it begins looking in the current directory, followed by two asterisks, which tells it to search recursively through all subdirectories. I'll add *.ts to the end, so that it looks for all files ending in ts. This would work fine, but there are other valid TypeScript file extensions, and the compiler will look for all of them if you just specify a single asterisk for one of the path segments, so I'll change *.ts to just *. I'll go back to my terminal, so I can run the compiler and use the new project file. I'll change into the app directory that contains the inherited config file, and then just type tsc to start the compiler. We can tell it correctly found and used the base file because it's running in watch mode, and that's one of the options that was only in the base file. I'll go back to my editor and again, open the output JavaScript file below the source TypeScript file. Notice that the comment line in the TypeScript file no longer exists in the JavaScript file. That was the one compiler option I added to the inherited file, so we know it's applying the inherited file to the base file successfully. It also compiled the correct file, so the glob I added is also working as expected. Setting up inherited configuration files is probably overkill for a project this small, but it certainly has advantages for larger projects, so I wanted to show you how they work. In the last demo in this module I'll quickly show you how you can configure a Visual Studio build test to run the compiler for you, so that you don't have to start it with the tsc command in a terminal.
Demo: Compiling with a Visual Studio Code Build Task
In this demo I'll show you how to set up the Visual Studio Code build task that I'll use throughout the rest of the course to start the TypeScript compiler. This is just a quick, handy tip for Visual Studio code users. Setting up a build task can save you a lot of keystrokes and, in this course, it will save you from having to watch me manually jump into a terminal and start the compiler so often. I'll open the command pallet and type the word task to search for the commands related to task. I'll choose the first option, Configure Task Runner. I now have to specify what kind of task runner I want to use. Since my project is configured to use TypeScript project files, I'll select the TypeScript tsconfig.json option. That creates a new file in the .vscode folder named tasks.json. The task will effectively just run the TypeScript compiler as a shell command for me. The command it will run is tsc. It's currently configured to be past the -p compiler option. That's the shorthand notation for the --project option that lets you specify the exact location of the tsconfig file you want the compiler to use. For the sake of clarity, I'll change the shorthand notation to --project, and let it know the tsconfig file it should use is in the app folder. I want to see the results of the command running, so I'll change the showOutput option from silent to always. I'll now execute this task from the command pallet. I'll search for build, and then select Run Build Task. Note that I can also start it with the keyboard shortcut, Ctrl+Shift+B. I'll probably most often use that for the remainder of the course. Once the task starts the output window in Visual Studio Code opens, and we can see that the compilation was complete and that the compiler is watching for changes to the source files. There's also an integrated terminal that can save me the trouble of switching apps, just to run the npm script that starts the web server. I'll open it and run the command npm start. It successfully started the web server, and I can now jump over to a browser, and access the app by going to localhost:8080. That's generally the workflow I'll use throughout the course to start the compiler and web server before checking out our code changes in a browser.
Summary
In this module I've covered lots of the basics you need to start building a TypeScript application. We took a look at the TypeScript website, and I showed you how to install TypeScript with npm, and run the compiler from the command line. TypeScript's compiler options give you a lot of flexibility regarding how your code should be interpreted, and what the output JavaScript will look like. We used a few of them, and you saw how you can find and apply other options as you need them. Finally, I showed you how to configure a tsconfig project file. It's one of the first things you'll do when starting any new project. They make it easy to run the TypeScript compiler repeatedly with all the options you need, and save you lots of typing by storing them in a single location. Now that you have a foundational understanding of how to install and use the TypeScript compiler we're ready to dive into the actual syntax of the language. In the next module I'll cover TypeScript's built-in types and how you can use them to prevent errors and make writing code faster and easier.
Taking Advantage of Built-in Types
Introduction and Overview
Hey everybody. Welcome back. It's now time to dive into some TypeScript syntax and begin experimenting with some of the languages built-in types. In this module we'll look at some of the primitive data types available in TypeScript. I'll also hopefully convince you to set aside the var keyword you may have used in JavaScript and now declare all of your variables with let or const. While I'm declaring those variables I'll also show you how to apply type annotations to them. Null and undefined have been a source of bugs for JavaScript developers for years. I'll show you how TypeScript allows you to manage them much more effectively, and I'll wrap up the module with a look at control flow-based type analysis, which I think is one of Typescript's most impressive features. There's lots to cover. Let's start with a look at some of the basic types available in TypeScript.
Basic Types and Variable Declarations
TypeScript supports all of the primitive data types available in JavaScript and adds a few of its own. Booleans are available in just about every programming language. TypeScript is no exception. These are just simple true false values. The number data type in JavaScript represents a floating point value, just as it does in JavaScript. Strings are basic text data. Just like in JavaScript literal strings can be surrounded by either single or double quotes. TypeScript also supports string templates, which are instead surrounded by the back tick character. I'll show you an example of that in a demo later in this module. Arrays are the primary data structure available in the language. They work very much like arrays in most other programming languages. One basic TypeScript data type not currently available in JavaScript is enums. Enumerations lets you give names to a finite set of numeric values, so that they're easier to use in your code. I'm not going to go over examples of all of these right now, since I'll be using most of them in the demos throughout the course. Let's now take a look at how to declare variables in TypeScript. I first want to show you a syntactically valid declaration in JavaScript. In this example I'm using the var keyword to declare a new variable named someString, and then I'm just assigning a string literal to it. I'm then logging the value of that variable to the console. The log statement appears in the code before the variable declaration. This works because JavaScript runtimes effectively move all variable declarations to the top of the code block in which they're declared. This is known as hoisting. Even though it's syntactically valid, I think it's very confusing. These two lines of code are right next to each other, but imagine trying to read some code if the declaration was many lines below a variables actual usage. Let's now look at the same two lines of code again, but this time I'll substitute the let keyword in place of var. Variables declared with let may not be used prior to their declaration, so this code will actually generate a TypeScript compiler error. I'll again, show you the same two lines of code using let, but I've now swapped their order. This works great and is the recommended way to declare variables in TypeScript. I also think it's much more intuitive when reading the code to see the declarations before they're used. Declaring constants works the same way. The only difference is that you use the const keyword instead of let. This is also a recommended practice. Using const when you know a value should never change is not only less confusing, but it lets the compiler do the work of enforcing your expectation that the value will never change, and that means one more class of bug that can be eliminated from your code.
Type Annotations and Type Inference
Type annotations are another important part of variable declarations that I didn't include on the last slide. They're a way of specifying what data type must be assigned to the variable. Here I'm declaring a new string variable named x. I've annotated the variable with the string type. Annotations are added by placing a colon after the variable, followed by the type you want the variable to store. Now that I've declared this variable as a string that's the only type I can ever assign to it. If I were to assign a different type to it, like a number, I'll get a compiler error. I want to point out that you're not required to add type annotations to your declarations. Here I've declared a new variable and also initialized it with a string. I named it y and left off the type annotation. Even though I didn't provide an annotation the variable still has a specific type. The compiler infers the type based on the value I used to initialize the variable. Therefore, this variable will also be a string. If I try to assign a number to it I'll get a compiler error. The TypeScript compiler is very good at inferring the type of variables, so whether you choose to use annotations is somewhat a matter of coding style. However, I want to make the case that you should use them for the sake of clarity, and to make it easier to read for the developers that may be reading your code in the future, which may even include yourself. It's easy enough to glance at the two variables I've declared here and immediately know their strings, but what if I declare a new variable and assign it the return value of some function? It may not be immediately obvious what data type the compiler will infer from my z variable here. In many editors you can hover over the variable or the function and find out what types are assigned or returned, but even though this is perfectly valid syntax I prefer to be more explicit and use type annotations. I personally like to see the type right there with the declaration. Even though I recommend using type annotations, I encourage you to decide on a standard format for variable declarations with your team and be consistent, no matter which format you decide to use. Okay, let's now go experiment with some of these techniques in the demo app.
Demo: Using let and const with Type Annotations
The TypeScript compiler is great at pointing out problems in your code when it sees you using types incorrectly. In this simple demo I'll show you a few examples of that as I declare some variables with let and const. I'm here in the app.ts file, and I'm going to start by writing a function that will log the name of a player to the browser's console. I'll call it logPlayer, and it will take one parameter, which will be the player's name. I'm going to use a template literal to log a short message and the name that was passed to the function. I want to quickly show you how template literals work in case you haven't seen them before. This was one of many features included in the ES 2015 version of JavaScript and adopted by TypeScript. It's truly just a technique for embedding expressions in literal strings. The string itself is surrounded by the back tick character. It's not a single quote. You can then include expressions in the string by adding the dollar sign character in the expression surrounded by curly braces. This will effectively insert the value of the name variable in this string when it's logged to the console. I think this is a much cleaner way to build strings based on variable values. Okay, I now want to call this function from inside the startGame function above. I'll use the let keyword to declare a new variable named playerName, and use the type annotation to declare that it will be a string. I'm just going to initialize it with a hard coded name for now. Note again the placement and syntax of the type annotation. I just added a colon after the variable name, followed by the type I want to assign to the variable. I'll now call the logPlayer function, and pass it the playerName variable. This code should work fine just like this, but let's make a few changes to it, and see how the compiler responds. I'll first remove the type annotation. Even without it there the compiler can infer that the type of the variable is a string, based on how it's being initialized. I'll hover over the variable name, and the little pop-up lets me know it's a string. I prefer to use type annotation, so I'll put it back on there. I'm now going to swap the order of these two lines of code. When I paste the function call that uses the playerName variable ahead of the variable declaration I immediately get a red squiggly line indicating I've got a problem in the code. I'll hover over the error, and it tells me that the Block-scoped variable playerName is used before its declaration. I'm getting this error because I declared the variable with let. If I change let to var you can see that the error goes away; however, like I mentioned before, this is potentially confusing having the declaration after the reference. I'll change it back to let, and put them back in the proper order. The strong type checking provided by the compiler is also going to help us out by preventing us from assigning obviously invalid values to the variable. It's declared as a string, so if I try to assign a number to it I get another error letting me know it is not assignable to type string. I get a similar error if I try to assign a Boolean value to it. However, if I just assign a different string to it, then the error goes away. Let's suppose for a second that I don't really want the initial string value to change. To enforce that I'll change let to const on the declaration, and you can see I get a new error when trying to assign it a different value. I'll hover over this one, and I'm told, cannot assign to playerName because it is a constant or a read-only property. TypeScript's doing a great job of enforcing my intentions. I'll delete that reassignment, which puts us back to the original version of the code. I'll now build it and run it to make sure it works fine in my browser. I'll press Ctrl+Shift+B to start my build task in Visual Studio Code. I got no errors, and the compiler is now watching for changes. I'll now go to the Terminal tab and start my web server with the command, npm start. Now that it's running, I can hope over to my browser and open localhost:8080. My log statement is being sent to the browser console, so I'll open the developer tools in Chrome. It's already on the Console tab, so we can see the output. I'll click the Start Game button, and we do in fact see the new log output. Let's now go back to the slides and talk about some other primitive types available in TypeScript.
Additional Built-in Types
Earlier I showed you a list of some of the most common primitive data types in TypeScript. You'll almost certainly use strings, Booleans, and numbers more than other types, but there are others you need to be aware of. The void datatype is used to represent the absence of a type. You'll most commonly see it used as the return type for functions that don't actually return a value. Null and undefined each have their own type in TypeScript. By default, the compiler will allow you to assign null and undefined to all other types, which will feel very familiar to JavaScript developers; however, TypeScript does provide the option to exclude null and undefined from other types. Having primitive types for each of them allows you to selectively allow them for certain variables as needed. I'll show you examples of that in just a minute. The Never type is a bit unusual and not something you'll use too often. It's the type assigned to values that will never occur. For instance, if you have a function that will never return, maybe because it throws an exception or just kicks off an infinite loop, the return value for that function will be never. Any is the type you use when you effectively want to opt out of type checking by the compiler. Types are obviously a huge part of TypeScript, but it's important to point out that they are an optional feature. Declaring a variable with the any type means that you can assign any value to it. Even if you're committed to using types, this can be handy if you're working with a third party JavaScript library, and you're not guaranteed a specific type back from a library function.
Union Types and the --strictNullChecks Compiler Option
I want to dig deeper into the role null and undefined play in the type system, but in order to set the stage for that I need to explain union types. They're a way of defining several types that may be assigned to a variable. In this line of code I've declared a new variable named someValue that may contain either a number or a string. In other words, the type assigned to the variable is the union of the number and string types. You specify the types to be unioned together using the vertical bar character between each of the possible types. I can now assign a number to this variable, and we'll get no complaints from the compiler. I can immediately follow that up by assigning a string to the same variable, and the compiler is happy to allow that as well. However, the acceptable values are limited to those in the union. Trying to assign the Boolean value true will cause the compiler to complain. Okay, now that you have a basic understanding of union types, let's turn our attention back to TypeScript's handling of null and undefined. By default, null and undefined can be assigned to any type. That's the way JavaScript works, and it's led to countless bugs. That can now be avoided in TypeScript by using the strictNullChecks compiler option. With that option set to true null and undefined are no longer valid values for every other type. You must explicitly opt in to allowing them by including the null and/or undefined types in a union type. Let's look at a few examples. In all of these examples we'll assume I'm using the strictNullChecks option. Here I've declared a new string variable. I could obviously assign a string to it, but if I try to assign null to it I'll get a compiler error. The same is true if I try to assign it undefined. The compiler option is named strictNullChecks, but it really applies to null and undefined. If I actually do what the ability to assign null to a variable I need to include it as a possible type in a union with the other valid types for the variable. I can now assign null to this variable without offending the compiler; however, undefined is its own type, distinct from null, so attempting to assign it to this variable will still generate an error, since I didn't include undefined in the union type. You can union as many types together as you'd like though, so in this last example I've declared a variable that can contain a string, null or undefined. Assigning null to it works just fine, as does assigning it undefined. I really like the strictNullChecks option. I think it will help eliminate a lot of runtime errors, but you can easily opt back into allowing null or undefined with union types. You've got lots of flexibility.
Type Assertions
Sometimes, for one reason or another, you might know more about the type of a variable than the compiler does. Maybe you're using a third party library that uses dynamic types or you have an object literal that was never assigned a specific type. If you find yourself in this situation you can effectively assign it the correct type with a type assertion. For example, here I've got a variable named value that I declared with the any type. Remember that the any type just means that any value can be assigned to the variable. I'm initializing this variable to the number five. I could later assert that the variable should actually have the number type. Doing so would let me take advantage of all of the TypeScript related benefits you've already seen, such as code completion in my editor, type checking, and things like that. The syntax for asserting a type is to place the type inside angle brackets in front of the variable. Here I'm asserting that the value variable is a number. I've wrapped the assertion in parentheses, so that I can then call a number method immediately after I've asserted that as its type. The toFixed method returns a string, which I can then just log to the console. There's also an alternative assertion syntax that does the exact same thing. You can also use the as keyword after the variable, and then specify the type it should be. Here I've wrapped value as number in parentheses, just as I did for the other assertion above, so I can call a method on the resulting number. The result is the same. Which syntax you choose to use is really just a matter of which one you prefer. Let's now jump into a demo, and turn on the strictNullChecks compiler option, and see how to use another type of assertion that's a little different than those I've shown you here.
Demo: Writing Better Code with the --strictNullChecks Option
The strictNullChecks compiler option really helps eliminate a lot of errors related to null and undefined that have plagued JavaScript developers for years. In this demo I'll turn it on in the MultiMath app, and show you how it can make your code better. The first thing I'm going to do is remove the initialization of the playerName variable I created in the last demo, and change it's declaration from using const to using let instead, so I can demonstrate assigning some different things to it. The project is not currently configured to use the strictNullChecks compiler option, so I can assign null to the variable, and I don't get any complaints from the compiler. Similarly, I can assign it undefined with no errors. I'll delete that line and point out one more thing about the two lines that remain here. I'm currently declaring the playerName variable, but not assigning it a value. I'm then immediately using it by passing it to the logPlayer function. JavaScript wouldn't stop me from doing this, and the TypeScript compiler isn't stopping me right now either, even though this is almost certainly not what I really want to do. Let's now turn on the strictNullChecks option, and see how it helps us spot problems like this in our code. I'll close this file for now and open the tsconfig.base.json file in the root of the project. That's where most of the compiler options I'm using are specified. I'll add the new option at the end of the list here, and set it's value to true to enable it. The compiler is currently running in watch mode, but I need to restart it, since I've made a change to the options in the tsconfig file it's using. I'll use the VS Code command pallet to search for the command to terminate the build task that started the compiler. I'll terminate that, and then immediately restart it by pressing Ctrl+Shift+B. When the compiler runs I immediately get several errors here in the output window, so let's go back to the code and see what's going on. Okay, I've got several of the dreaded red squigglies here in my code letting me know where the problems are. I'll hover over the first one on the playerName variable. This error I expected. It's letting me know that I'm trying to use the player name before it's been assigned a value. That's pretty helpful, so I'll go ahead and assign it a value. I'll assign it null. Assigning it null before adding the compiler option worked just fine, but now it gives me an error. Type null is not assignable to type string. Assigning it undefined also no longer works. I think using the strictNullChecks option is great, but if there are times you still need to assign null or undefined to a variable you can declare it with a union type. I'll change the declaration of playerName to be the union of string and undefined and the error on the assignment goes away. In this case, I really don't want to allow undefined, so I'll change the annotation back to just a string. The error obviously returns when I do that. I'll point out that most of these problems can be avoided if you get in the habit of declaring all of your variables with the proper type annotation and immediately initialize them. I'll go ahead and add the initialization back to the playerName declaration. Okay, let's move on to the next error. It's only messagesElement variable storing the HTML element I retrieved by calling getElementById. The error is that the object is possibly null. What it's really letting me know here is that I'm referencing a property, innerText in this case, on an object that has the potential to be null, which would generate a runtime error. If I hover over the declaration for the variable you can see that TypeScript has inferred that null is a valid value. It figured that out by knowing the return type for the getElementById function. Even though it's a valid value, the compiler is letting me know I still may be using it inappropriately. The first thing I want to do here is clean up the variable declaration itself. I'll first change var to let, and then I'll add a type annotation to make it clear what type it is. I want to pause for just a second and talk about this HTML element type. It's not a TypeScript primitive, but it is one of the types that comes with TypeScript. The language has wrapped classes and interfaces around many of the objects used in web development to facilitate strong type checking. We'll see more examples of this later. This is just a quick preview. I'll right-click on the type, and choose Go to definition. VS Code opens a file named lib.d.ts in the path you see here. Inside that file you see there's an interface named HTMLElement, and if I scroll down just a bit you can see that it has a property named innerText, which is the property I'm referencing in the code. I'll show you lots more about interfaces later in the course. I just didn't want you wondering what that object was and thinking I was skipping something important. Okay, I'll close this file for now, and get back to the error we need to fix. So the error on the messagesElement variable is TypeScript letting me know that I may be trying to access the innerText property on an object that may be null. In this case, I know it won't be null because I'm sure that the getElementById function will return a valid HTML element. I know there's an element with the id messages; therefore, I want to assert that the element will in fact not be null. I can do that with the non-null assertion operator. You use it by adding an exclamation mark after the variable, which you want to assert will not be null. Once I add it to this variable you can see that the error goes away. What it does is effectively remove null from the list of valid types. To make this a little more obvious, I'll quickly create a new variable named element. I'll assign it the messagesElement variable with the nonNull assertion operator added to the end of it. If I hover over the new variable you can see that its type is just HTMLElement, not the union type with null that was assigned above to messagesElement. Okay, there's one error left, and it's really the same one I just fixed. It's worried I'm calling the addEventListener function on an object that might be null. I know the call here to getElementById won't return null, so I'll add the non-null assertion operator after it. I want to point out that my use here of this operator is conceptually similar to the type assertions I showed you in the slides. I'm asserting that I know more about a particular type than the compiler does. When I do that it adjusts the type and let's me proceed. So the strictNullChecks option definitely found some issues, but I was able to easily fix them. I really like it and encourage you to use it knowing that you can easily implement exceptions to the things it finds if you need to. Let's now take a quick look at control flow-based type analysis.
Demo: Understanding Control Flow-based Type Analysis
So far in this module you've seen some of the benefits of the TypeScript compiler strong type checking. In this last demo I want to show you one of its neatest tricks, known as control flow-based type analysis. This type of analysis is the compiler analyzing the conditional behavior in your code, and applying type checking to your variables using the most narrow type possible in any given code branch. Let's look at an example. I've declared the messagesElement variable here to be a union type of HTMLElement and null. Right after that I'm going to add an if block just to illustrate the analysis performed by the compiler. For the condition I'll check to see if messagesElement is null. Inside the block I'll just return the variable. It's not important what I have it do, but I'll hover over the variable inside the block, and you can see that TypeScript no longer reports it as a union type. Instead its type is just null. That's because based on the condition I used the type can only be null at this point inside the if block. The TypeScript compiler recognized that and knows that HTMLElement is no longer a valid type for the variable right here. I'll add an else block, and just put another meaningless reference to the variable inside it. In this case, we know the variable is not null, so if I hover over it inside this block the compiler reports its type as HTMLElement. It's no longer a union type that includes null since it can't possibly be null at this point. The compiler has narrowed it to the most specific type possible, however, I can re-widen the type to any of the types specified in its initial union type declaration. To do that I'll copy the call to getElementById I used to initialize it, and reassign that to it in the else block. I'll hover over this new assignment, and TypeScript again reports that it can be either an HTML element or null. I'll again, hover over the line right above this, and at that point it can still only be an HTMLElement. I think it's pretty impressive that the compiler can analyze your code this way and always type check against the most specific type possible. Do note that you can't widen a variable beyond the types in its initial type declaration. If I try to assign a number to the messagesElement variable I get an error. I'm still constrained by the initial declaration.
Summary
In this module we've looked at several different TypeScript features, but I think they all relate to a few primary themes that you see when working with TypeScript in general. I hope you saw how the language can help you reduce confusion and increase clarity. The let and const keywords force you to declare variables before you use them, and give you the ability to declare them as constants if you know their values should never change. Type annotations also increased clarity, making it clear to other developers what type a particular variable should have. Also, the ability of the compiler to perform control flow-based type analysis reduces confusion by letting you work with the most specific type available at that point in your code. We also saw how TypeScript helps you reduce unintended consequences and increased stability. This is particularly apparent when using the strictNullChecks compiler option. Null and undefined have been the source of countless bugs in JavaScript code. With TypeScript you now have the ability to exclude them as valid values, and the compiler can alert you if they're used inadvertently. Finally, even though these features may sound like constraints if you've been a JavaScript developer for many years, TypeScript lets you maintain the flexibility to create exceptions to these constraints and do what you need to do depending on the circumstance. For instance, you can use a union type to specify several valid types for a particular variable. You can also use type assertions or the nonNull assertion operator to give the compiler more information about the variables you're working with. In the next module I'll show you how to use more type annotations and unique syntax to write better functions in TypeScript.
Writing Better Functions with TypeScript
Introduction and Overview
Hey everybody. Functions are the building blocks of any application written in JavaScript or TypeScript. In this module I'm going to show you the features in TypeScript that help you create better functions that are easier to use and more fun to write. Because this is TypeScript I'm going to start out by showing you how you can add type annotations to your functions. Adding annotations to functions gives you all of the same benefits as adding annotations to variables, but they also make your functions easier to use. We'll then see how to write arrow functions in TypeScript. They're an alternative function definition syntax that's commonly used when writing anonymous functions. I'll wrap up this module by demonstrating how functions themselves have a type defined by their signature, and how you can take advantage of that in your code. Let's first see how to spice up a boring function by adding type annotations to it.
Adding Type Annotations to Functions
Before I show you how to add type annotations to functions let's quickly look at a plain old JavaScript function with no annotations. I've named it dullFunc. It's hard to know how to use this function based on the function's signature you see here. It takes two parameters, but we don't know anything about their types. Function parameters are one case where TypeScript can't infer types, so these two parameters will implicitly be given the any type, which means that any values can be passed into the function, and we'll get no type checking support from the compiler. Just based on the signature, we also can't tell what type, if any, the function returns. I'll have it return a string, which the TypeScript compiler can use to provide type checking support when calling the function. However, it would be nice as a developer to not have to read the code in the function to figure out what it returns. I'm sure it's pretty obvious by now, but I'm not a fan of code like this. Let's look at a better way of writing functions. I've named this function funFunc, and it has lots of additional information that helped the compiler and developers that need to use it. Just like the function above, it takes two parameters. However, I've added type annotations to these. Annotations are added to parameters using the same syntax used with variables. The first parameter is named score, and is followed by a colon and then the type of the parameter, a number in this case. The second parameter is named message, and it's declared as a string. It has one additional little feature though. Notice the question mark character that appears immediately after the parameter name. This flags the parameter as optional. In JavaScript all parameters are optional and you can even get away with passing more parameters than a function expects. Thankfully that's not the case with TypeScript. All parameters are considered required unless you specifically flag them as optional. It's important to note that an optional parameter must appear after all required parameters in the functions signature. You'll also get an error if you pass more parameters than a function expects. I think both of these checks will help you find problems in your code during development rather than at runtime. The last annotation I use specifies the return type of a function. You do that by adding a colon after the closing parentheses around the parameters, and then add the type the function will return. All of these annotations give the compiler lots of information it can use to help you spot problems with how the function is used. In the body of this function I'm not using the parameters I passed in, but I hope you can agree with the returned value. Type annotations on functions are helpful, and you should definitely use them.
Using the --noImplicitAny Compiler Option
On the previous slide I showed you this dull function and mentioned that without type annotations the two function parameters would be implicitly assigned the any type. Using the any type implicitly or explicitly effectively turns off type checking for that variable or parameter. If you want to avoid accidentally using an implicit any type, then you can use the noImplicitAny compiler option. You can still explicitly annotate a variable with the any type using this option, but if you have any implicit any's then you'll get errors like these letting me know the two parameters for my dull function implicitly have the any type. This is a helpful and firm reminder from the compiler that I forgot to add type annotations to my parameter.
Default-initialized Parameters
The last trick I wanted to show you related to parameters are default-initialized parameters. Here I've got a simple function named sendGreeting that takes one parameter named greeting. I've annotated it as a string, but I've also added an equal after the annotation, and then assigned it the default value, Good morning. This effectively makes the parameter optional and assigns it the default value if no value is passed to the function. Also note that since this function doesn't return a value I've specified void as its return type. With the default parameter in place I can call the function without passing in a parameter, and it will print out Good morning. However, if I call it with a specific value, like Good afternoon, then it will assign that to the parameter and not use the default value. Both of these are perfectly valid. Careful use of default-initialized parameters gives you the flexibility to call a function without passing every piece of data it may need, but they can also smooth over accidentally leaving off a parameter here and there. Let's now go take advantage of some of these techniques in the demo app.
Demo: Adding Type Annotations and Default Parameter Values
In this demo I'll add type annotations and some default parameter values to the functions in the MultiMath app. Before we get started writing any code I'm going to open the tsconfig.base.json file here at the root of my project, and change one of my compiler options. I already have the noImplicitAny option listed here, but it's currently set to false. I'm going to change it to true, so that the compiler will alert me whenever it has to implicitly assign the type any to a variable. I'll now press Ctrl+Shift+B to start my build task and compile all of my code with this option enabled. Okay, changing that option only exposed one place in the code I need to work on. On line 12 of app.ts the compiler is telling me that parameter 'name' implicitly has an any type. I'll close all of this stuff, and then open the code and show you how we can fix that. Visual Studio Code is again, making it easy for me to spot the error with the red squiggly under the name parameter on the logPlayer function. If I hover over it we see the same error that I got from the compiler output in the terminal. This was pretty easy to fix. I know this parameter should really be a string, so I'll just add a colon in the string type after the parameter name to properly annotate it. Now the compiler is happy again. No more red squiggly. While I'm here I'll also add a type annotation for the function's return value. It doesn't return anything, so I'll just add a colon and the void keyword after the parameter list. Okay, I now want to write a couple of new functions that will help me capture user input in the app, and display the user scores on the screen. Before I start writing them I want to quickly jump back to the index.html file and point out a couple of things that I'm going to be taking advantage of in the code. The first thing I want to point out here is the input box prompting the user to enter their name. There's nothing special about it, it's just a regular text input box, and I gave it the id playername. I'm going to write a function that will let me pass in the id of an input box, and get back the value in the box. The other thing I need to do in this file is give myself a place to output the scores. I'll jump down to the end of this file. I've already got a div element here where I want to display the scores, but I'm going to add an id value to the h4 element, so I can easily reference and add the new scores to it. I'll call it postedScores. Now I'm ready to write some new code. I'll go back to app.ts and create a new function named getInputValue. I'll pass it the id of one of the HTML input elements, and I'll annotation that parameter with the string data type. If the function finds a value in the HTML element it will return a string. Otherwise, I'm going to have it return undefined, so I'm using this union type as the return type for the function. I'll use a code snippet to paste in the body of the function. The first thing this code does is call the getElementById function with the id that was passed to the function. I showed you in the last module that this function normally returns a value with the type HTMLElement; however, in this case, I know I'm going to pass it ids of elements that are actual input elements, so I'm adding a type assertion that will treat the element it finds as the more specific HTMLInputElement type. I'm assigning the found element to a variable named inputElement. The HTMLInputElement type has a property on it named value that lets you retrieve the value in the inputElement. If the value of the element is an empty string, then this function will return undefined; otherwise, it will return the value the user provided. Okay, so there's a nice, new, fully annotated function that's going to make it much easier for me to retrieve user input values. Let's now write another one to post scores to the screen. I'll name this one postScore, and it will take number and string parameters for the score and name of the player that received the score. It won't return a value, so I'll give it the void return type. I use another code snippet for the body. I'm first getting a reference to the element with the posted scores id I showed you in the index.html file. I then just print the score and the player name to the screen by assigning a template string to the innerText property of the HTMLElement. Note that I'm using the nonNull assertion operator I showed you in the last module to assert that the posted scores element won't be null. Okay, I'm now going to come back up here to this startGame function, and write just a few lines of code to test out the new functions. I don't have any code to actually play a game yet, but I want to call the new functions, just to see if they work as expected. I'm first going to delete these last few lines that just displayed a couple of welcome messages. I'll now call the new postScore function and pass it 100 as the score and the value and the playerName variable I set above. Since I now have a function to retrieve values from input boxes I'm going to remove the hard coded playerName and replace it with a call to the getInputValue function. I'll pass it the playername id I showed you in the index.html file. That's now giving me an error because the return type of the function is the union of string and undefined. I'll fix that by changing the type of the variable to match the function. That cleared up that error, but I now have two new ones just below it when I call logPlayer and postScore. The problem is that both of those functions are expecting strings, but the type of the variable I am passing to them is a string or undefined. I'm going to fix that on the postScore function by changing the function to make the playerName parameter optional. I do that by just adding a question mark after the parameter name. Making it optional means it's now okay to pass it undefined. I'm going to fix the logPlayer function a little differently. I'm going to assign a default value to the name parameter. I'll set it to MultiMath Player. Remember that using a default initialized parameter also makes that parameter optional, which means that passing in undefined is now perfectly valid, and the error on the function call goes away. Let's now run this code and see how it actually behaves. I'll open the terminal in Visual Studio Code and start the web server with the command, npm start. I'll then jump over to my browser and go to localhost:8080. I want to see the output in the console, so I'll open the developer tools. I'll type a name in the Player Name box and click the Start Game button. Everything looks good so far. The hard coded score with the correct player name appears on the screen, and the log player function logged the correct value in the console. I'm now going to remove the name from the Player Name box and click the Start Game button again. Because of the way I wrote the getInputValue function this is effectively causing undefined to be passed to the logPlayer and postScore functions. You can see in the console that the logPlayer function used the default parameter value I specified in the function definition. However, the behavior of the postScore function was not as nice. I didn't get any errors, but printing undefined to the screen is not really what I want. Let's go back to the code and fix that. Just to demonstrate that passing undefined is really the same thing as passing nothing, I'm going to change the call to postScore to not pass the second parameter at all. I'll also change the score I'm passing, just so we can see that it's different. I'll then change the function itself to use a default parameter like the logPlayer function. I'll remove the question mark, making the parameter optional, and then add the same default value I used with logPlayer. Remember that adding a default value also makes the parameter optional. I'll hop back over to my browser and refresh the page. I'm going to leave the Player Name box blank and just click the Start Game button. This time we see that both functions use the default parameter values. Do note that those default values were applied under slightly different circumstances. The logPlayer function was passed a value, but the value was undefined. The parameter was completely omitted from the call to postScore, but in both cases a default value was applied. I'll quickly go back to the code and add the playerName variable back to the call to postScore. Okay, let's now go talk about arrow functions.
Anatomy of an Arrow Function
Arrow functions are another syntactical feature added to the ES 2015 version of JavaScript and adopted by TypeScript. They're also known as lambdas in other languages. They're really just anonymous functions with a very simple syntax, even though they may look a little strange until you get used to them. The general form of an arrow function is the parameters on the left, followed by an arrow, and then the function body to the right of the arrow. You may occasionally hear the arrow referred to as a fat arrow, since it begins with an equals symbol rather than the more undernourished dash symbol used in other languages. The equals symbol is immediately followed by the greater than symbol. Let's look at a few examples. I'm first going to declare a new variable named squareit, and assign it an arrow function. Even though it's very simple, everything here on the right side comprises an entire function. Remember that everything to the left of the arrow are parameters, and everything to the right is the function body. The x here is the parameter to the function. That's followed by the arrow, which is followed by the body of the function. In this example the function body is just one line of code that multiplies x by itself. The function body can contain more than one line, but if it did I would need to surround those lines with curly braces. Arrow functions like this with a single line body and no surrounding curly braces have an implicit returned statement. Even though the return keyword isn't used, the result of multiplying x * x will be returned from the arrow function. I can call this function by just using the name of the variable I assigned it to followed by a pair of parentheses containing the value I want to pass as the function parameter. This next example is similar, but this time the arrow function takes two parameters named a and b. If you have more than one parameter you must surround them with parentheses like I've done here. Other than that small difference, this example is pretty much like the first one. There is a single line function body, so the result is implicitly returned. I can call it using the name of the variable I assigned the function to. There may be times you want to create an arrow function that doesn't take any parameters. In that situation you just put an empty pair of parentheses in place of actual parameters. You then call the function with another empty pair of parentheses, like you would with any other function that doesn't take any parameters. The thing to remember here is that if your arrow function takes 0 or more than 1 parameter, then you must use the parentheses in the function declaration. If it only takes one parameter, then the parentheses are optional. The exception to this rule is single parameters that also include a type annotation. They must also be surrounded with parentheses. I'll show you an example of that in a demo shortly. Let's now look at a slightly more complex example that filters an array. Here I've got an array of numbers named scores. I want to filter that array into a separate array of high scores. So that I can fit everything on my slide, I'll declare a new array named highScores before I actually assign anything to it. I'll then assign it the result of calling the filter function on the scores array. Arrays have a built-in function named filter that itself takes a function as a parameter. That makes it a perfect candidate for an arrow function. The function passed to the filter function takes three parameters. The first is the element in the original array being processed. The second is the index of that element in the array, and the third is the original array. Because there is more than one parameter I've surrounded then with parentheses. The parameter list is followed by the arrow, but rather than a single line function, like I showed you earlier, this function has multiple lines of code, so I have to surround them with curly braces. The function will be called once for each element in the original array, and should return true if that element should be included in the filtered array. I'm checking to see if the value in the element is greater than 100, and if so, inexplicitly returning true. Notice that because I have multiple lines of code I have to use the return keyword to return a value from the function. Let's now see how to convert a traditional function to an arrow function in a demo.
Demo: Converting a Traditional Function to an Arrow Function
In this demo I'm going to convert a traditional function to an arrow function with type annotations. I'm going to start by writing a very basic function. I'll name it logMessage, and it will take one string parameter and return void. The body of the function will just log the message that was passed to the function. Arrow functions are anonymous functions, so in order to convert this function to an arrow function the first thing I'm going to do is get rid of the name logMessage. I'm going to assign the function to a variable, so I can actually call it, and I'll reuse logMessage as my variable name. Arrow functions don't use the function keyword, so I'll delete that. I'll then add the arrow before the function body. Since this function body just has one line of code I don't really need the curly braces surrounding it, so I'll remove those. Okay, this looks good. This is a syntactically valid arrow function I can call using the variable name I assigned it to; however, I want to show you a couple of other things. Even though I love type annotations I have to admit there are lots of times where they aren't normally used. Specifying the return type for an arrow function is one of those times. I can remove the void return type, and TypeScript can still infer the return type based on the body of the function. You may even see code that doesn't apply an annotation to the parameter, but let's see what happens if I take that off in this case. I get a red squiggly under the parameter name. I'll hover over the error, and you can see that parameter 'message' implicitly has an 'any' type. I'm getting this error because earlier I enabled the noImplicitAny compiler option, and the compiler can't infer the type of this parameter. Therefore, I'm going to put the type annotation back on there. Before I run this code I want to be clear that I don't recommend defining ordinary functions as arrow functions in this way. I just wanted to walk through the process of converting a traditional function to an arrow function, so you can see how one maps to the other. I think traditional functions are still easier to read when you're writing functions that will be called from multiple places. However, arrow functions are nice when you need to pass an anonymous function to another function, which happens a lot in JavaScript and TypeScript development. Okay, I can now call my new arrow function using the variable name, logMessage, followed by the required parameter in parentheses. I'll switch to my browser, refresh the page, and you can see the new message appear in the console. Let's now go talk about function types.
Demo: Taking Advantage of Function Types
In this quick demo I'll demonstrate how TypeScript functions have a type of their own and how you can take advantage of that in your code. The first thing I'm going to do is delete this call to logMessage I added in the last demo. However, I'm going to leave the function itself. I'll then write a new function named logError that takes a string parameter and uses the error function on the console object to report the error passed to the function. This new function, like all TypeScript functions, has a type of its own that's defined by the type of all of the function parameters and the function's return type. I'll hover over the function name, and a little pop-up shows me the function type. It takes a string parameter and returns void. It just so happens, that is the same type as the logMessage arrow function I wrote in the last demo. I'll hover over it, and you can see that it also takes a string parameter and returns void. What this means is that if I declare a variable to have that same function type I could assign either of these functions to that variable. Let's give it a try in the postScore function. The first thing I'm going to do is declare a new variable named logger. You can use function types as type annotations just like you use string, number or any other type. To give this variable a function type I just add a colon after the variable name as usual, and then put the expected function parameters and their types in parentheses, followed by an arrow and the expected function return type. This logger variable may now be assigned any function that takes a single string parameter and returns void. Just like the logMessage and logError functions I wrote earlier. I'll now add an if block to check the value of the score parameter. If the score passed in is less than 0, then I'm going to assign a logError function to the logger variable, since I want this condition reported as an error. Otherwise, I'll assign the logMessage function to the variable. At the end of this function I'll then use the logger variable to call whichever function got assigned to it. I'll just pass it a template string that will output the score. So that we can see an example of each condition I'm going to add one more call to postScore above in the startGame function, and pass it -5 as the score. I'll go back to my browser and refresh again. I'll quickly add a player name, and then click the Start Game button. The first call to postScore used a score value greater than 0, which caused the logMessage function to be called. You can see the result here in the console. The second call to postScore used a negative score, which caused the logError function to be called. It called the error function on the console object, which caused the output to appear as an error here in the console. This was all possible because of the fact that the logMessage and logError functions have the same function type.
Summary
TypeScript functions aren't fundamentally different from JavaScript functions, but I hope you've seen in this module that they're easier to use. By adding type annotations to parameters and function return values your code is easier for other developers to read, and the TypeScript compiler can make sure the functions are called correctly before you get a runtime error in your production environment. However, it's also notable that if you decide you need the flexibility that some appreciate about JavaScript, then you can use TypeScript features like optional parameters and default initialize parameters to allow functions to be called with fewer parameters and also know you'll have a valid value for those not passed. You've also seen how TypeScript's support for arrow functions with type annotations provides a nice clean syntax you can use when writing the anonymous functions that are so common in client-side web and node development. I've spent a lot of time so far in the course showing you how to use and annotate your code with TypeScript's built-in types. In the next module we'll dive into classes and interfaces, and you'll learn how to create your own types. Stay tuned.
Creating and Using Custom Types
Introduction and Overview
Hi. This is Brice Wilson. So far in this course I've shown you lots of ways to use the built-in TypeScript types that help you write better code with fewer errors. It turns out that you can get all of the same great type checking support with your own custom types as well. Object oriented developers should feel right at home with the topics in this module, but don't worry if it's all new to you. I'll cover everything you need to know. Creating your own custom types is really all about two TypeScript language features, interfaces and classes. It's only two features, but they're really big features. I'll first explain the differences between the two and when you might use one over the other. I'll then get into the details and show you how to create and use them in your apps. Along the way we'll take a brief detour as I show you how to configure TypeScript projects to support multiple source files, but let's start by looking at the differences in classes and interfaces.
Interfaces vs. Classes
Interfaces and classes can both be used to create custom types in TypeScript. They're similar, but also different from each other in a few very significant ways. First of all, interfaces and classes are both used to define new types. Interfaces have properties, but they just define the signature of the property. What this really means is that the property only has a name and a type, which is often all they need. Classes also have properties, but unlike interfaces they can also provide implementation details for the property. This usually takes the form of custom accessor functions. Experienced object oriented developers will know these as getters and setters. Interfaces and classes also both define methods. Similar to the properties they define, interface methods are just a method signature. They define the number and type of parameters the method expects, as well as the method's return type; however, they don't provide any implementation. That has to be done by an object that implements the interface. I'll show you how to do that a little later. Class methods do include full method implementations and each instance of the class will get the same code for a given method. The final difference between interfaces and classes that I want to highlight here is that interfaces can't be instantiated. They effectively define a contract. If you have an object that you know implements a given interface, then you know the object will have all of the properties and methods defined on the interface; however, you can't use the interface to actually create an object with those properties and methods. Classes can be instantiated. You can use the new keyword I'll show you shortly to create new instances of a class. Each new instance is an object that has all of the properties and methods defined on the class. I think a useful analogy to use when trying to understand the differences between interfaces and classes is that of building a house for yourself. When you first start thinking about the kind of house you want you'll probably think about it in terms of the features you want. Maybe you know you want three bedrooms, two bathrooms, and a front porch. This is a high level abstraction of the house you'll ultimately end up with. You're defining some of the properties of your future house, but not the details, such as total square footage or the dimensions of each bedroom. This abstract house you have in mind is very much like an interface. It has the properties and methods sketched out, but you don't have all the details. Deciding on a specific house includes all of the details that were lacking when you first imagined the house you wanted. Specific houses or plans for houses, like this one here, have precise dimensions, and you know exactly what you'll get when the house is built. Just like a class, it includes all of the implementation details, so that you know exactly what the finished product will look like. Interfaces are useful when you only need to describe the shape of an object without all of those details. Maybe you'll want to implement a particular interface differently in different parts of your application. Interfaces are also great if the object you need to describe doesn't have any behaviors, usually implemented as methods. If you just need a list of properties that define some particular type of object, then an interface is perfect. Let's now see how to create one.
Creating an Interface
I'm first going to create a very simple interface for an Employee. Interfaces are defined by using the interface keyword followed by the name you want to give the interface. The body of the interface is surrounded by curly braces. I'm going to give this interface two string properties, name and title. Any object that implements this interface must have those two properties with those two data types. Let's now look at a slightly more complex example. Here I've defined a Manager interface. I've used the extends keyword to specify that this interface will extend the Employee interface above. That means that this interface will inherit all of the members defined on the interface it extends, so already we know this new interface will have name and title properties. I'm going to add to that a property for the manager's department and the number of employees he or she manages. Even though they're not all explicitly listed here, we know this interface now has four properties. I'll also add one method to it. Remember that methods on interfaces don't actually provide any implementation, just a signature. The signature is defined by assigning a function type to the method like I showed you in the last module. The type of this scheduleMeeting method is a function that takes one string parameter and returns void. It would be up to an object that implements this interface to provide the actual code that executes when the scheduleMeeting method is called. Because they don't provide any implementation details you'll often hear that interfaces only define the shape of an object. The shape of an object is very important when working in a language that uses a structural type system like TypeScript. Let's talk about that a little more next.
TypeScript's Structural Type System
Structural type systems, like the one used by TypeScript, use the structure of objects to determine their compatibility. For instance, let's look again, at the Employee interface I showed you on the last slide. It has two properties, name and title. Let's now suppose I declare a new variable and just assign it an object literal. This object has three properties. The first two match the property names and types to find on the employee interface. Because it has all of the required properties this object implements the Employee interface and may be used anywhere an employee object is expected, even though I haven't done anything to explicitly declare that it represents an employee. The third property on the object literal doesn't exist on the interface, but that doesn't mean it doesn't still implement the interface. As long as it has all of the required members, the interface is implemented, and the object can have whatever additional members it needs. Therefore, I can declare a new variable to have the Employee type, and and a sign of the object literal that implements the interface, and I'll get no complaints from the compiler. All of this is possible because TypeScript implements a structural type system. As long as the structures match, then you can treat the object as the type with that structure, even if it wasn't explicitly declared with that type. This is often referred to as duck typing. If something walks like a duck, swims like a duck, and quacks like a duck, then it must be a duck. Let's now add some interfaces to the demo app.
Demo: Creating Interfaces
In this demo I'm going to add a couple of interfaces to the MultiMath demo app and demonstrate TypeScript's structural type system. I want to start building out the app in a more structured way, so I'm going to begin creating new files for the interfaces and classes I create. It's common practice to create a separate file for each of them, just to make it easier to find what you're looking for later. I'm going to start by creating a new interface to represent a result object that can contain a result for each game played. I'll first create a new file in the app folder named result.ts. I'll add to it a new interface named Result. I'll use a code snippet to paste in the members I want to add to the interface. This is a pretty basic interface. It's just four properties and no methods. I want objects that implement this interface to be able to store the name of the player, the score they got in the game, the number of problems in the game, and the multiplication factor they used. This is a perfect example of a case in which an interface will almost certainly meet my needs. I could have created a class with the same properties, but since I don't have any methods to implement, and I suspect I'll be using object literals to implement the interface, I don't really need the additional features offered by classes. Let's now create a slightly more complex interface, just to demonstrate some other techniques. I'll create a new file named person.ts that will house a new Person interface. I'll paste in the members and you'll notice a couple of differences from the result interface. The first thing I want to point out is the question mark character after the age property. In the module on TypeScript functions I showed you how the question mark can be used to mark a function parameter as optional, and it really does the same thing here. Adding a question mark after an interface member name marks it as optional, and objects that implement the interface aren't required to implement that member. I've specified the type of the age property to be a number, but if I hover over it you can see that because it's optional the compiler automatically turns it into a union type with number and undefined. The other thing to notice about this interface is that one of its members is a function that takes no parameters and returns a string. This member is not optional, so any object that implements the interface will have to provide an implementation for that function. Later in this module I'm going to show you how to create a class that will provide an implementation, but for now I'm going to show you how to do it with an object literal. I'll go back over to app.ts and create a couple of new variables that use these interfaces. I'll first create a variable named myResult, and declare that its type will be the result interface. I'll initialize it to an empty object literal. The compiler's not really happy about that assignment. I'll hover over the red squiggly under the variable name, and it tells me that the empty object is not assignable to type 'Result'. Property 'playerName' is missing. Since playerName is a required property on that interface, and I've declared the myResult variable to have that interface type, then anything I assign to it must have a playerName property. I'll quickly add a playerName property to my object literal, and assign it a value. I'll again hover over the error, and this time it reports that the property score is missing from the object, so I obviously need to make sure I include all of the required properties from the interface. I'll paste in the remaining ones, and you can see that the error on the variable goes away. Let's now do something similar with the person interface. I'll declare a new variable named player, and assign it the Person type. Assigning it an empty object will cause errors similar to those I showed you on the myResult variable, so I'll go ahead and add a name property and the formatName function. Remember that the formatName function took no parameters in return to string. I'm going to implement that function with about the simplest arrow function I can imagine. It takes no parameters and returns the hard-coded string, Dan. With those two properties added to the object the error on the variable goes away. Remember that the Person interface also includes an optional age property, but since it was optional I can leave it off this object without getting any errors from the compiler. The last thing I want to mention about interfaces is that they're a design time tool. They're used by the complier to type check your code, but they don't compile down to anything in JavaScript. To prove that I'll press Ctrl+Shift+B to start my build task. It looks like the compilation was successful. I'll now expand the js folder that contains all of my compiled output. I'll open the person.js file, and you can see that the only thing in it is a reference to the sourceMap. That's only there because I have the sourceMap compiler option enabled; otherwise, the file would be completely empty. Okay, let's now go back to the slides and talk more about classes.
Class Members
Let's take a look at class members and some of the features they support that don't exist on interfaces. Probably the most obvious example of that is method implementations. Classes include complete function definitions for the methods on the class. They also include property implementations; however, these will often look just like the non-implemented properties on interfaces, since many properties are nothing more than a property name and a data type. However, TypeScript does support property accessor functions, which let you write custom code to define how a property is set or retrieved. Accessor functions are often referred to as getters and setters and definitely provide property implementations that can't exist on interfaces. Class members can also include one of a number of access modifiers that control the accessibility of the member. By default, all members on TypeScript classes are public, but there is also a public keyword you can use before a member name if you prefer to be more explicit. Members may also be declared as private if you don't want it to be accessible outside the class or protected if it may only be accessed inside the class or any classes that inherit from the class. Let's look at an example. I'm going to define a new class named Developer. I just used the class keyword followed by the name I want to give the class. The body of the class goes inside a pair of curly braces. I'll first give my new class a public string property named department. Remember that class members are public by default, so I don't need to prefix this declaration with the public keyword. Next, I'll declare a private member named title. I like to prefix all private member names with an underscore as a quick visual clue that the member is not public. In this case, I did use an access modifier, placing the private keyword in front of the declaration. My plan is to use this private member as a backing variable for a pair of accessor methods, so I'll add them next. Accessor methods allow you to customize how you get and set data for a property. In this example I'm using accessor methods for a property named title. The syntax for accessors is pretty much like any other function with the exception of the get and set keywords. Here I've placed the get keyword in front of the function that will return the value for the property. The name of the function is the name I want to give the property. The getter shouldn't take any parameters and should return the type of the property. In this example I'm returning the value in the _title variable. The setter function is prefixed with the set keyword, and is also given the name you want to use for the property. It should take one parameter, which is the value being assigned to the property. Here I'm taking the value passed in and converting it to uppercase before storing it in the backing variable. If I wanted to make this property read-only I would just write the getter and not implement a setter. One important piece that I want to point out here is my use of the this keyword. Whenever you want to refer to a class member that belongs to the class you're in you need to prefix the reference to the member with this followed by a period. I will admit this can take some getting used to. I also write a lot of C#, which doesn't require this extra reference. Jumping between it and TypeScript still occasionally leaves me scratching my head when I get a TypeScript error, only to realize I forgot to include this in front of a class member. Class methods are nearly identical to any other function you might write. The one exception is that you don't include the function keyword in front of the function definition.
Extending Classes and Implementing Interfaces
I showed you earlier how you can extend an interface. You can do the same thing with classes. Here I'm using the extends keyword to specify that the WebDeveloper class extends the developer class. This creates a very typical object oriented inheritance relationship between the two classes. The developer class on the previous slide had department and title properties, as well as a document requirements method. This class inherits those members and adds a favoriteEditor property and a writeTypeScript method. I can create an instance of this class by declaring a variable and using the new keyword followed by the name of the class to call the classes constructor and create a new instance. I'll talk more about constructors in just a little bit. I can use the new instance to set properties defined on the base Developer class or the WebDeveloper class. I mentioned earlier that interfaces don't provide any implementation details. It's up to objects that implement the interface to provide those details. A common practice is to design classes to implement interfaces. Here I've got an employee interface that has a couple of properties and a method named logID that takes no parameters and returns a string. I'll now add a new class named Engineer that implements that interface. I declare that this class will implement the interface using the implements keyword right after the class name. That's followed by the name of the interface it will implement. Once you've declared that a class will implement an interface you're required to provide an implementation for all the required properties and methods on the interface. A basic implementation of the two properties looks just like the property definitions on the interface, although I could have used accessors if I needed to implement some custom logic. The function requires a little more code, since I need to provide a function body that returns the expected string type. Let's now go add some classes to the MultiMath app.
Demo: Creating Classes
In this demo we'll continue adding functionality to the MultiMath app by creating a new class for one of the objects we need to work with. In the last demo I created a Person interface and showed you one way to implement that interface with an object literal. I now want to show you how to implement it with a class. I'll start by adding a new class to the app folder named player.ts. I want to use this class to represent a player in the game, so I'll simply name it player. I'll then use the implements keyword to specify that it will implement the person interface. I'll use a code snippet to paste in the body of the class. The class members I've pasted in match the members I defined on the interface; name and age properties and a method named formatName. You may recall that I made the age property on the interface optional by adding a question mark after the property name. Therefore, I'm technically not required to have an age property on the class. I'm going to include it, and I want to quickly show you that classes support the same syntax for optional members. I'll add a question mark after the property, and if I hover over it you can see that TypeScript has now included undefined as a possible type for the property, along with number. If I take the question mark off and hover over it again, you can see that the type is now just number. Classes that implement interfaces must implement all of the required members from the interface, but they may also include additional properties not defined at all on the interface. As a quick example of that, I'll add a highScore property to this class and declare that it will be a number. I'll now jump back over to app.ts and write some code that uses the class. I'll first get rid of these variables I created earlier while demonstrating interfaces. I want to create an instance of the player class, so I'll first declare a variable named firstPlayer and assign it the Player type. I'll then initialize it to an instance of the Player class. You do that by using the new keyword followed by the name of the class and a pair of parentheses. With a new class instance assigned to the variable I can begin using the class members on the instance. I'll set the name property on the instance using the dot notation you use with any object. I'll also use a log statement to output to the console the value returned from calling the formatName method on the instance. Before I attempt to run the code in my browser I want to quickly open the compiled version of my player class. Unlike the compiled interface file I showed you earlier, TypeScript classes do produce actual JavaScript code. Interfaces are just a design time construct, but classes produce real output that must be delivered to the browser for your app to work. I'll open the terminal and type npm start to fire up my web server. I'll then hop over to my browser and go to localhost:8080. Everything looks okay so far, but if I open the developer tools you can see that I've got an error in the console, an Uncaught ReferenceError: Player is not defined. The problem here is that the Player class was defined in its own TypeScript file and compiled to its own JavaScript file, but the JavaScript file isn't being referenced anywhere in my HTML. It's common in TypeScript to define individual interfaces and classes in their own files, so this is a problem we need to know how to resolve. I could just add a reference to the additional JavaScript file, but I want to show you a different technique that I think will scale better for larger projects. I'll do that in the next demo.
Demo: Configuring a Project with Multiple Source Files
Although it's not technically required, it's common practice to store each interface and class in their own files. Therefore, I thought this would be a good time to show you how to build applications that easily support multiple TypeScript source files. Here you can see the code I wrote in the last demo that creates a new instance of the Player class that's defined in a separate file. The player.ts file compiled down to a file named player.js. I'll quickly open my index.html file and show you that currently I am only referencing the app.js file. That's why I got an error in the last demo. I am attempting to use the code in player.js, but the code in that file isn't being sent down to the browser. I could easily fix that by referencing the file here, but I want to show you a different technique that I think works better for mid to large size applications. I'm going to open the tsconfig.json file in the app folder that I configured earlier in the course. I specified which files I want to compile by using the include property and passing it a blob that instructs the compiler to compile all TypeScript files in this directory and all subdirectories. This is the reason I was able to use the Player class from inside my app.ts file without complaint from the compiler. The compiler was compiling both files, so it recognized the code in person.ts and didn't give me any errors when I used it in a separate file. I'm going to change this to make my instructions to the compiler a little more precise. I'll delete the include property and replace it with a different property named files. It's a simple array of all the files you want the compiler to compile. I'm just going to pass it the app.ts file, since it's the starting point in the application. In order to have this take effect I'm going to reload my Visual Studio Code window. My build task is currently running, so I'm also prompted to terminate that. Once everything is reloaded I'll jump back over to app.ts, and now you can see that I'm getting errors on the line of code where I'm using the player class. That's because I've only instructed the compiler to compile the app.ts file, and it doesn't know what player is anymore. I'll hover over the error, and it just says, Cannot find name 'Player'. I'm going to fix that by using a triple slash directive. I'll jump up to the very top of this file and add one. Visual Studio Code comes with a code snippet named ref that makes it easy to add triple slash directives. They're essentially just single line comments that must appear at the very beginning of a file in order to provide additional instructions to the compiler. They can be configured to do various things, but I'm going to focus on their most common use, which is to provide a reference to another file. You can see that the code snippet added a simple XML element named reference after the three slashes, and there's one attribute named path. That's where I'm going to provide the path to the file I want to reference. In this case, that will be the player.ts file. This effectively tells the compiler that the code in this file is dependent on the code in the player.ts file. That will cause the compiler to automatically compile player.ts whenever it compiles app.ts. I'll jump back down to the end of the file, and you can see that the error has now gone away. Player.ts will now be compiled with this file, so the compiler knows about the player class I'm using. I also need to add a similar reference in the player.ts file. I'll open it, and you can see that I'm getting an error when I try to use the Person interface the class implements. This is for the same reason as the last error. I'm not explicitly compiling the person.ts file that contains that interface, so the compiler doesn't know about it. I'll add a triple slash reference at the top of this file to reference person.ts. I hope you can see that this is beginning to create a chain of dependencies. My tsconfig.json file only instructs the compiler to compile app.ts, but that file references player.ts; therefore, the compiler compiles it and sees that it references person.ts, and that gets compiled as well. Using this technique means each code file can specify the other files it depends upon, so you can be much more precise about what you actually compile. Just to prove that's what's happening, I'll open the terminal in Visual Studio Code and go into the js folder that contains all of the compiled output for the app. I'll delete all of the files in that folder. I'll now restart my build test by pressing Ctrl+Shift+B. The compilation was successful. I'll now expand the js folder in the sidebar, and you can see that even though my tsconfig file only instructed the compiler to compile app.ts I also have compiled versions of person.ts and player.ts because they were referenced with triple slash directives. Note also that I don't have a compiled version of the result.ts file anymore, since I'm not currently referencing it anywhere. I think this is a better way to control which files get compiled, but it doesn't solve the problem of how to send all of the correct code to the browser. Rather than adding a new reference in my index.html file, I'm going to go back to my tsconfig file and include an additional compiler option named outFile. You can see here that the code completion help for it says that this option will concatenate and emit output to a single file. It effectively looks at all of the files that get compiled, and directs all of the output to a single file, and makes sure the code in that file is ordered properly. That's a very helpful piece that you have to do yourself if you attempt to manually add references in your HTML. I'm going to have it emit all of the output to a file named app.js in the js directory, since that's the name of the file I'm already referencing in my index.html file. Since I've changed my compiler options I need to terminate and restart my build task. Before I restart it I'm again, going to delete all of the files in my js output directory. I'll then start the build again, and now you can see that I only have a single JavaScript file named app.js in the js directory. All of the code for all required files got sent to that single file in the proper order. I'm not ready to test it out, so I'll restart the web server and then jump over to my browser. You can still see in the console the error I got in the last demo when the browser couldn't find the code for the player class. I'll refresh the page, and you can see that error goes away, and the console now displays the formatted player name I originally attempted to show. I think using triple slash directives are a much nicer way to manage dependencies in your TypeScript projects than just compiling and shipping everything. In the next module I'll expand on those ideas and show you how to use TypeScript modules. Before we get to that, though, there are a couple of more topics related to classes I want to cover.
Static Members
By default, class members are accessed after first creating an instance of the class and assigning it to a variable. You then access the member of the class by using the variable name followed by a dot and the member name. Static class members are an exception to this pattern. They're members that you access on the class directly, not on instances of the class. Here I've got a class named WebDeveloper that extends the Developer class. I'm first going to add two static members to this class, a property named jobDescription, and a method named logFavoriteProtocol. The syntax is identical to any other property or method declaration, with the exception of the static keyword that appears just before the member name. There will now only be a single instance of each of these members, and they will exist on the class itself. I'll add another method to the class that will log the value in the job description property. Normally, if you want to refer to another class member you use the this keyword in front of the member name; however, with static members you use the class name, since the member exists on the class. The same is true if you want to call static methods. Rather than creating a new instance of the class to call a method you just use the name of the class followed by a dot and the name of the static member. Static members are a nice way to add utility or helper methods that are related to the purpose of the class, but aren't dependent on any data that might be stored in instances of the class.
Constructors
The last topic I want to cover related to classes is constructors. They're a special type of function that's executed when new instances of the class are created. They're a handy place for you to perform any special initialization or set up that needs to be performed for new instances of the class. Let's look at a couple of examples. Constructors appear inside the body of a class just like any other class member. The syntax is very similar to other methods, except the name is always constructor. This is about the simplest possible example of a constructor. It takes no parameters and just logs a message letting us know a new developer instance is being created. I should mention that you aren't required to add a constructor function to your classes. If you don't need to perform any special initialization, then you're free to leave it off. Let's look at a slightly more complex example. The constructor for this class has a few more interesting pieces. First of all, you can see that it accepts a string parameter named editor. This means that when new instances of the class are created with the new keyword a string must be passed to the constructor at that time. The first line inside the body of the constructor calls a special function named super. Based on the name, you may expect this function to execute some truly extraordinary code. That may or may not be the case. Calling super is how you call the parent classes constructor from a child class. This webDeveloper class extends the Developer class. The class being extended is sometimes referred to as the parent class or the super class. If your class extends some other class, and your class has a constructor, then you're required to call super, as I've done here. The last line of code in this constructor assigns to the favoriteEditor property the editor parameter that was passed in. Initializing properties like this is one of the most common ways to use constructors. Notice that I've put a modifier I haven't yet discussed in front of the favoriteEditor property. The readonly modifier does exactly what you would expect. It prevents the value of the property from being changed once it's set. Properties declared as readonly may only be initialized when they're declared or inside a constructor, as I've done here. Another useful feature of constructors that can save you some typing is to use parameter properties. I'll show you how to use them in the next demo.
Demo: Refactoring the Demo App with Classes
In this demo I'm going to refactor much of the code in the MultiMath app to use classes. Along the way I'll take advantage of a static member and show you how to use parameter properties. I'm going to start here in the app.ts file. I want to move the getInputValue function I wrote earlier into it's own utility class, so I'm first going to copy it to the clipboard. I'll add a new file to the project named utility.ts. I'll create a new class in it simply named Utility, and then paste in the function. Because the function is now part of a class I don't need the function keyword in front of the function name. I want to be able to use this function throughout the app without having to create new instances of the utility class, so I'll add the static keyword in front of it. I'm now going to refactor it just to simplify things a bit. I'll remove the undefined from the union return type, so that it now just returns a string. I'll just have it return the value of the HTMLInputElement it finds, and then get rid of the if else blocks below. Okay, I now want to add a scoreboard class I can use to display scores to the screen. I'll create a new file named scoreboard.ts. I know it will need to use the result interface I defined in result.ts, so I'll add a triple slash directive at the top of the file that references it. Next, I'll stub out the class, and then add a private member named results that will be an array of objects that implement the result interface. I need a way to add new results to the array, so I'll add a new method named addResult that takes a result as a parameter and pushes it onto the array I just defined. I'll then use a code snippet to paste in another method named updateScoreboard. Its job is to generate the HTML representing all of the results and putting them on the screen. I declare a variable to hold the HTML and then just loop over the results array adding information from each result to the HTML. I then get a reference to the element on the page named scores, and add the new HTML to it. That's all I need it to do right now. The last class I want to add is a class to represent each game the player plays. I'll create a new file named game.ts. I know this class is going to need references to several other files, so I'll paste them in at the top. I'll then stub out the Game class, and paste an initial set of properties and a constructor. What I've got here is actually a very common pattern that you'll see as you start creating more classes with constructors. It's helpful to have your constructors take parameters, which you then use to initialize properties on the class. That's exactly what I'm doing here. I've got a private member named Scoreboard I'm initializing to a new scoreboard instance, but after that I've got three properties named player, problemCount, and factor, which I'm just setting to the values passed into the constructor. This is so common that the smart folks working on TypeScript created a shortcut to save us some typing. The first constructor parameter named newPlayer receives the value that's then just assigned to the public player property. I'm going to remove the parameter name and replace it with the name of the property I really want to receive the value, and then put the public access modifier in front of it. This effectively declares the new class property and initializes it with the value passed to the constructor. I can now delete the line above that declares the property and the line inside the constructor that assigns it a value. The code is getting smaller already, and I haven't changed the functionality at all. I'll now do the exact same thing for the problemCount and factor properties. These properties are known as parameter properties, since they're really created as part of parameters being passed to the constructor. You just need to add an access modifier in front of the property name in the constructor's signature. All of mine here are public, but you can use private, protected, and readonly as well. It's now much tidier. I now need to paste in a couple of methods on this class. The first one is named displayGame, and will generate the HTML that displays the game board on the screen. It uses the problemCount and factor properties on the class to determine exactly what the board should contain. I'm not going to go over all of the HTML it generates, but I do want to point out my use of the built-in string constructor. The factor property is a number, and I need to convert it to a string to display on the screen. The StringConstructor can be passed any value, and it will attempt to convert it to a string. Once the HTML was generated I added to the page and then enabled the calculate button. The other function I'm going to add is named calculateScore. It's going to read the answers provided by the player and create a result object that can then be added to the scoreboard. It loops over all the problems and compares the provided answer to the correct answer in this if block. If they match it increments the value of the score variable. I then declare a new variable named result that will implement the result interface, and set it equal to object literal that has all the correct properties assigned values from this particular game. Once I have that result object I can use my scoreboard instance to add the result to the scoreboard and call the updateScoreboard function that will update the screen. The last thing I do here is disable the calculate button to get ready for the next game. Okay, that's all the pieces that I need to make the game work. I now just need to wire it all together in the app.ts file. I'll go back to it, and I'm going to delete all of the code in here and start fresh using my new classes. The first thing I know I need is references to the player and game classes, so I'll add those to the top. I'll then declare a variable to represent the new game. I'll use a code snippet to paste in the event handlers I need for the start game and calculate buttons. When the Start Game button is clicked I create a new player instance, and set its name to the value provided by the user. Notice that I'm using the static getInputValue function by calling it directly on the Utility class without first creating a new instance of the class. then retrieve the number of problems and the multiplication factor to use from the input boxes. I'm using the built-in number constructor to convert the input strings to numbers. The number constructor is very similar to the string constructor I showed you earlier. The last thing I need to do here is create a game instance and assign it to the new game variable by passing the player, problem, count, and factor values to the constructor. Calling the displayGame function will display the game board on the screen. The event handler for the calculate button just calls the calculateScore method on the newGame instance. That should be all I need. Let's jump over to the browser and see how it works. I'll refresh the page, and then add my name as the player. I'll use the default factor of five, and just do three problems in this game. I'll click the Start Game button, and the simple game board is displayed. I'll now answer each of the, and click the Calculate Score button. The scoreboard gets updated, and it reports that I got three out of three correct for factor five. Everything is working, and we finally have a fully functional version of the game.
Summary
I've covered a lot of material in this module. The headliner topics were obviously interfaces and classes. Getting type checking support for built-in types is great, but being able to get the same support for types you create is immensely helpful when working on large applications. I showed you lots of syntax examples and use cases for classes and interfaces, but along the way you also learned about related topics, such as how TypeScript's structural type system works, and how to configure a project to use multiple source files, and only compile those that are needed. Most of all, I hope you saw the continuation of a theme, which is flexibility. TypeScript is tremendously flexible. You can choose to create custom types or not. You can choose to compile a lot of individual files or a single file. You can also choose different techniques for delivering those files to the browser. I'll show you another option for that as the theme of flexibility continues in the next module, which covers TypeScript modules. Stay tuned for that.
Creating and Consuming Modules
Introduction and Overview
Hey everybody. Welcome back. In this course module I'm going to cover TypeScript modules. TypeScript adopted the very straightforward ES2015 module syntax, and I'll show you how to use it, along with some features specific to TypeScript that will help you create and consume modules in your applications. I'll start off with a brief explanation of why you might want to use modules in TypeScript. Since not all browsers natively support modules yet I'll also quickly go over some of the supporting technologies you may have to employ in order to use your TypeScript modules in a browser application. I'll then cover the syntax you use to import and export modules and wrap up with an explanation of how TypeScript resolves the location of the modules you import.
Why Use Modules?
In the last course module I finished building a fully functional version of the MultiMath demo app that didn't use modules at all. Therefore, it would be very reasonable to ask why they're even needed. Well, for very small apps it may not make much of a difference, but as your app gets larger modules provide a number of benefits. They allow you to encapsulate implementation details inside the module and only expose a carefully considered API for other modules to consume. This gives you the freedom to refactor code inside the module and not effect consumers of the module, as long as the API for it doesn't change. Modules are also easily reusable. If designed to implement a very specific piece of functionality they can be used throughout an app or dropped into other apps as needed. I think perhaps the most important benefit of modules is that they allow you to think about your apps in terms of larger building blocks. They create higher level abstractions you can use when discussing and planning your application. I found this can be tremendously helpful when working on large projects. Modules are great, but depending on your runtime environment they can require some supporting technologies. I'll talk more about those next.
Supporting Technologies
The ES2015 version of JavaScript was the first version of the language to provide native support for modules. Prior to that there were several popular non-native module formats used with browser and node applications. If you need to integrate your code with older applications or if your runtime environment doesn't get support native modules, then you may need to employ some additional technologies to get everything working. Thankfully, it all starts with writing TypeScript using the built-in module syntax, and then running it through the compiler. You use the module compiler option to specify which JavaScript module format the compiler should output. TypeScript adopted the ES2015 module syntax, but using the module option you can have the compiler output AMD, CommonJS, UMD or System modules in addition to the ES2015 format. Unless your runtime environment natively supports ES2015 modules, you'll then need the help of a module loader or bundler to run your code. Node will natively load CommonJS modules, but if you're writing a browser app you may need to use the popular RequireJS or SystemJS libraries to load modules for you. Another option is to use a module bundler, like Webpack, which will prepare your modules to execute in a browser as part of a build step. All of these topics are a little beyond the scope of this course, but I will show you a very simple SystemJS implementation that will allow me to use modules in the MultiMath demo app. If you'd like to learn more about module formats, loaders, and bundlers, then I'll quickly plug another Pluralsight course of mine titled, JavaScript Module Fundamentals. It covers all of those concepts and uses a JavaScript version of the same MultiMath demo app as this course, so check that out if these concepts are new to you. Let's now move on to the syntax required to use modules in TypeScript.
Exporting and Importing
The API exposed by a module is defined by the items that are exported from the module. Modules can export just about any of the TypeScript constructs we've used in this course; functions, classes, interfaces or even simple variables. Let's look at a snippet from a file named person.ts. Let's suppose it has an interface named Person that I want to make available to other modules. I can export the interface by just adding the export keyword in front of the interface declaration. It will then be available to be imported by other modules. I've left off the body of the interface just to save some space on the slide. You can also export function and class declarations the same way. Notice that the Employee class also includes the default keyword after the export keyword. This specifies that this will be the default item exported from this module. It's available for import just like any of the other items here, but if the importing module didn't specify the name of a particular item to import from this module, then it would be this class that would be imported. If there are functions, classes or other things I want to use in this module, but not have them exposed to other modules, then I can just define them as I normally would, and leave off the export keyword. That effectively makes them private to this module. Export statements serve much the same purpose as exporting a declaration. It's just a different syntax. Let's suppose I have the same person.ts file from the last slide, and what to export the same items from it with an export statement. Rather than placing the export keyword in front of each declaration I want to export, I just use the keyword once followed by a comma separated list of the names of the items I want to export inside curly braces. This list of exports doesn't have a default export like those in the last slide, but I am able to take advantage of an additional trick using this syntax. Notice that I added the as keyword after specifying that the employee class should be exported. That's followed by the alias I want to use for the exported class. The effect of this is that I can use the name Employee for the class inside this module, but it will appear to other modules that import it as StaffMember instead. If there are lots of items in a module you want to export, then using an export statement like this can be nice because you can easily see in one place exactly what's being exported. Otherwise, I think it's easier to just add the export keyword to the declarations. Use whichever method you prefer. To use the functionality exported from a module you must import it in the consuming module, and there are several different ways you can import from a module. In this example, I'm importing two of the exported items in the person module from the previous slide. I do this by using the import keyword followed by a comma separated list of the items from the module I want to import inside curly braces. Note that you're not required to import everything the module exports. You can just import what you need. After the list you use the from keyword followed by the module containing the items you're importing. This is what's known as a relative reference. Because it starts with dot slash TypeScript will look for a file named person with a valid TypeScript file extension in the same directory as the current file. I'll talk more about relative and nonrelative references when I discuss module resolution a little later. Once the items are imported I can use them like any other class, function or interface. Here I'm just declaring a new variable to have the Person type. Let's look at a few variations on this basic import statement. This example looks very similar, but notice that there is only one item being imported, and it's not surrounded by curly braces. This is one way you could import the default export from a module. The default export from the person module will be assigned the name Worker in this module regardless of what it was named in the Person module. Using this technique means I don't have to know the name of the default item being exported. I can just use an import statement and specify the name I want to give it. The default export from the earlier slide was the employee class, and I can create new instances of it here, like any other class, only here it's known as Worker. I can also import items and alias them to new names as part of the import statement. Here I'm importing the StaffMember class and specifying that I'll refer to it as CoWorker. I do that by using the as keyword followed by the alias I want to give it. Just like the previous example, I can use it like any other class here with that alias. The last example I want to show you here is how to import an entire module. Rather than listing the specific items you want to import from the module you use an asterisk followed by the as keyword and the name you want to use to refer to the imported module. Here I'm importing all of the person module and giving it the alias, HR. I can then access items in that module using the alias followed by a dot and the name of the item. Now that you've seen the basic syntax for working with modules, let's return to the demo app and refactor it to use modules.
Demo: Converting the Demo App to Use Modules
In this demo I'm going to remove the triple slash directives used to reference multiple code files in the MultiMath app, and convert each file into a module. I'm going to start by opening the tsconfig.json file, so I can make a couple of changes in it. I previously added the outFile compiler option, so that all of the JavaScript would be output to a single file. This meant I only had to reference one file in my HTML. The outFile option isn't compatible with all module formats, and I plan to use a module loader that will download individual modules as needed, so I'm going to remove this option for now. I'm going to add the module compiler option to specify which module format I want the compiler to output. I'm going to use the commonjs format. I'm now ready to start converting my individual code files to modules. I'll start in the utility.ts file. It currently contains a class with one static method. Just so that I can demonstrate exporting multiple items from a module, I'm going to change this slightly. I'm going to remove the class, and then change the static method to just be a standalone function. I want to have a second item to export, so I'll quickly add another function named logger that takes a single string parameter and just logs it to the console. I want to export both of these functions from the module. I could just add the export keyword in front of each of them, but I'm going to use an export statement instead. I write the export keyword followed by the items I want to export inside a pair of curly braces. Let's suppose I want the getInputValue function to be exposed to consumers of this module as getValue instead? I can do that by adding the as keyword after the function name and then the name I want other modules to import. I then just add a comma and the name of the other function I want to export. I'll export it with the original name. This is an export statement. The other modules in the app just contain a single item, so I'll use simpler export declarations with them. I'll do that next in the result.ts file. The code in here doesn't depend on anything else, so all I need to do is add the export keyword in front of the interface definition. That's it. Now this is the module. I'll open person.ts next. It needs the same treatment; just export added in front of the declaration. Next, I'll work on player.ts. It has a triple slash directive that references the person module. I'm going to remove that and add an import statement instead. The person module exports an interface named person, so I'll just add that interface name inside curly braces after the import keyword. I'll then give it a relative reference to the person.ts file in the same directory as the current file. Remember that I don't need to add a file extension because the compiler automatically looks for files with valid TypeScript extensions. That takes care of importing the person dependency. I'll now change the player declaration, so that it's being exported. Okay, next up is scoreboard.ts. It has one triple slash directive that references result.ts. I'll replace that with an import statement that imports the result interface. I also want to make sure I export the Scoreboard class. I hope you can see that the code to use modules is really no more complicated than the alternative of referencing files. I'll now open game.ts. It has a few more references, so I'll start replacing those with import statements. This module uses the getInputValue function from the utility module. Remember that I aliased that function to get value when I exported it, so when I import it here I need to refer to it as getValue. The utility module also contains another function, but I don't need it here, so I'll only import the one function I need. Next, I'll import Result and Player exactly as they were declared in their respective modules. The last thing I need to import is the Scoreboard class. I could do it just as I did Result and Player, but I want to show you an example of creating an alias as part of an import. I'll import Scoreboard as ResultPanel. I'm not really sure that's really a better name, but it will work for demonstration purposes. Now when I want to use that class in this module I need to refer to it as ResultPanel. I'll come down a couple of lines and make sure I export the Game class. I then need to change my Scoreboard references here to use ResultPanel instead. I've got one more change I need to make a little further down in the calculate score method. I was previously calling the static getInputValue function on the Utility class. That's now just a function being imported as getValue, so I'll update the call to reflect that. Okay, that's all of the changes I need to make in this file. The last file I need to update is app.ts. I'll first remove the triple slash directives at the top, and start replacing them with import statements. I'll import the Player and Game classes first. I also need the getValue function from the utility module, but I'm going to import it a little differently just to show you yet another technique. I'm going to import and alias the entire module. You do that by putting an asterisk after the import keyword and then using as to create an alias. I'll refer to the module as Helpers. I'm importing the utility module, but I'm going to refer to it in this module as Helpers. I'll now update the previous calls to getInputValue. In order to use the functions in the imported module I just type Helpers followed by a period, and you can see that Visual Studio Code is giving me some code completion help. It knows that the module I imported as helpers contains two functions. I want getValue, so I'll select it. I'm calling the same function in two more places below, so I'll just copy this call and paste it over the other two. Okay, that's it. I've now updated the app to use modules; however, I can't run it yet because I haven't configured a module loader. Before I do that I want to talk about how TypeScript resolves modules.
Relative vs. Non-relative Imports
In order to understand how TypeScript resolves the location of the modules you import you first need to understand the difference in relative imports and non-relative imports. Relative imports direct the compiler to a specific location on the file system where the module can be found. All relative references begin with either a slash, dot slash or dot dot slash to direct the compiler to the location of the file. Here the reference to the hardware module is preceded by a slash that tells the compiler the file may be found on the root of the file system. You can leave off the file extension, and the compiler will automatically look for files with valid TypeScript file extensions. These examples also use relative references. The compiler will look for the person module in the same directory as the current file, and it will go up one directory, and then look for the recruiting module inside the HR directory. Non-relative references are nearly identical, but they don't include any reference to a directory structure before the module name. These two lines import the jquery and lodash modules, but notice that they don't include any reference to paths. I'll show you how TypeScript finds the files referred to with non-relative references in just a second. In general, you should use relative references when referring to your own modules and non-relative references when referring to third party modules.
Module Resolution Strategies
TypeScript resolves the location of modules by first seeing if they're relative or non-relative references, and then attempting to locate the module using the configured module resolution strategy. That strategy is configured using the moduleResolution compiler option. The two possible values you can pass it are Classic and Node. The TypeScript documentation mentions that Classic mode is available primarily for backward compatibility, but I think it's important to have a general understanding of the differences between the two. The first thing to know is that the default value for the moduleResolution compiler option is dependent on what type of modules you've configured the compiler to emit. Classic mode is the default resolution strategy if you're emitting AMD, System or ES2015 modules. For all other module types node is the default resolution strategy. The process of resolving modules in classic mode is very simple. I'll show you some examples in a minute, but it's little more than traversing directories looking for the right module. Setting the mode to Node, as you might expect, closely mirrors the way Node attempts to resolve modules. It's not terribly complex, but there is certainly more to it than what happens with classic mode. Classic mode is also less configurable. The node strategy will read a package.json file if it's present, so it's somewhat more configurable. Let's now look at some examples of how each of these modes would attempt to resolve both relative and non-relative references.
Module Resolution Examples
Resolving relative imports in classic mode is pretty straightforward. Let's suppose this is a file named player.ts located in the Source/MultiMath directory. If I attempt to import the person module, and use dot slash in front of the module name to indicate it's in the same directory, then the first place TypeScript will look for that module is in a file named person.ts in the same directory as the current file. If it doesn't find that file, then it will look for another file in the same directory with a slightly different file extension. Notice that instead of the standard ts file extension this one has d.ts. This is another standard TypeScript file extension that's used with type definition files. They're a way to provide type information for JavaScript libraries you want to use in your TypeScript projects. I'll talk more about them in the next course module. That's it for resolving relative imports in classic mode. The compiler just looks for a file with one of two extensions in the specified directory. Non-relative imports in classic mode is only slightly more complex. Let's imagine I have the same scenario as before, but this time I've got a non-relative reference to the person module. The compiler will begin its search for that module with the same two files it searched with the previous relative reference. It will look in the current directory for a file named after the module with either a ts or a d.ts extension; however, this time if it doesn't find one of those it will go up one level in the directory structure and search for the same two files there. It will continue moving up the directory structure until it either finds one of the files or runs out of directories. Let's now look at using the node resolution strategy with relative references. Using the same example import statement, TypeScript would first look for the person module in the current directory with one of three valid TypeScript file extensions, ts, tsx, or d.ts. Tsx files are the TypeScript equivalent of the JavaScript jsx files used with popular frameworks like React. If those files don't exist, then the compiler will check for a file named package.json in a directory named after the module. If that file exists and it has a typings property in it that directs the compiler to the correct file, then that file will be used. Otherwise, the compiler will look in the current directory for a file named index, with one of the same three valid TypeScript file extensions. This is very similar to how Node searches for relative imports. The last example I want to show you here is the Node strategy with non-relative imports. I'll again, use the same sample import, but this time with a non-relative reference to the person module. The first place the compiler will search for this module is in a sub-directory of the current directory named node_modules. Inside that directory it will look for a file named person with one of the same three TypeScript file extensions I showed you on the previous slide. It will then look for a package.json file in a directory named after the module inside node_modules. It will check to see if the package.json file has a typings property that directs it to the location of the module. It will then look in the node_modules directory for a file named index with one of the valid TypeScript extensions. If the compiler still hasn't found the module at this point it will follow the same process up one level in the directory structure. Notice that it's still looking in a folder named node_modules, but it's now searching in the Source directory instead of Source/MultiMath. The same searches for package.json and index will be performed here as well, and then it will continue moving up the directory tree doing the same thing until it finds the module or runs out of directories. I realize all of this probably sounds a little overwhelming, but I want you to at least have some idea about how TypeScript attempts to resolve modules. That will make it much easier to figure out what's wrong when you inevitably get an error that it can't find something you're trying to import. There's actually a compiler flag you can enable that will output all of the locations the compiler is searching that can be very helpful when troubleshooting those kinds of problems. We'll take a look at it in the next demo, along with a few other more explicit ways you can tell the compiler how to locate modules.
Demo: Configuring Module Resolution
In this demo I'll show you some additional techniques for configuring module resolution, and how to trace the location as the compiler searches for modules. I'm going to start by configuring a module resolution strategy in my tsconfig file. I've already configured my project to output commonjs modules, so the default resolution strategy is already node, but I'm going to add the moduleResolution compiler option and make that explicit. In order to show you how non-relative references are resolved, I'm going to open the player.ts file and temporarily change the person import to use a non-relative reference. To do that I just remove the dot slash from the front of the module name. That immediately generates an error. I'll quickly hover over it, and you can see that TypeScript is reporting that it can no longer find the person module. As you saw in the slides, there are lots of different locations TypeScript searches for modules based on how you've configured your app and referenced your modules. It's easy to get confused and not understand why a module isn't being found. There's actually a handy compiler option you can turn on to help you troubleshoot those kinds of problems. I'll go back to my tsconfig file and turn on the traceResolution option. With that option enabled the compiler will output information about how each module import is being resolved. I'll start my build test, so we can see why it's having trouble finding the person module. I'll close the sidebar and make this output pane a little bigger. I will admit, this is not the most readable output, but if you take your time and look carefully it can help you figure out what's going wrong. I'll first scroll up to the top and show you the output for a module that was successfully resolved. The first line reports that it's trying to resolve a relative reference to the player module from inside app.ts. The next line reports the location the compiler is looking for the module. The last line for this import reports that the file was found, and that it'll be used for the resolution result. I'll now scroll down a little and find the attempt to import my non-relative person reference inside player.ts. It reports that it's resolving module person from player.ts and that it will use the node module resolution strategy. It then attempts to load the module from the node_modules folder, and you can see the attempts to find the file with the module name and any valid TypeScript extension. None of them exist. It then moves up the directory hierarchy, and eventually even looks for JavaScript files when it can't find any TypeScript files. Ultimately, it reports that module name 'person' was not resolved. I'll now go move the person.ts file to a new location, and see if I can get rid of the error. Most of the time you'll use non-relative references with third party libraries, so it makes sense the TypeScript would search through the node_modules folder. I'm going to temporarily move the person.ts file in there. I'll just go to the sidebar and drag the file down to the node_modules folder in the project. Now when I reopen player.ts you can see that the error is gone. I'll hover over the person module name and Visual Studio Code tells me where TypeScript found the module; inside the Source/multimath/node_modules directory. I now want to show you some additional options you have to inform the compiler about the location of your modules. I'm first going to create a new folder inside my app folder named modules. I'll then go drag the person.ts file out of node_modules and into the new modules folder. As soon as I do that you can see that the error comes back since the compiler, again can't find the module. I'll go back to my tsconfig file and add a new compiler option named baseUrl. This option allows you to specify a base directory that should be used when attempting to resolve non-relative module imports. I'll set it to my new modules folder. All non-relative imports will now happen relative to this baseUrl. In order for this change to take effect I need to terminate my current build task. I'll then clear the output window and restart the build task. Let's now look again, at the trace information for the non-relative reference. Here you can see that it's resolving the person module and player.ts. It then reports the baseUrl value that will be used for resolution. A few more lines down it reports that a person.ts file does exist inside the baseUrl location, and that it will be used as the resolution result. I'll hop back over to the code, and you can see that the error has gone away, and Visual Studio Code reports that the person module was found inside the modules folder. So, as you can see, the baseUrl compiler option is pretty handy if you need to configure a different location for the compiler to search for modules. There are a couple of other options you can use for module resolution that I want to mention briefly. I'll go back to my tsconfig file, and I'll use a code snippet to paste the two new options. The paths option works with the baseUrl option to let you configure how particular modules map to files. You assign it an object that contains key value pairs. The keys are the names of modules you want to map. The value for each key is an array of the locations the compiler should look for the module. It's important to note that all of the locations specified are relative to the base URL. The other option down below is named rootDirs. It's a good option to use when you have source files spread across multiple directories that you intend to combine into a single directory at runtime. The value you assign to the rootDirs option is an array of all of the directories that will be combined. If the compiler sees a relative import in a subdirectory of one of one of these directories, then it will search for the relatively referenced module in each of the configured root directories, since they're expected to be combined at runtime. The values I've provided here are just examples to show you how the options are configured. Okay, I wanted to quickly point out these options, but I'm not going to use them here in the MultiMath app, so I'm going to take them back out. I'll then move the person.ts file back into the app folder, and delete the modules folder I created. The last thing I need to do is go back to the player.ts file and change the person import to again use a relative reference. In the next demo I'll quickly configure a module loader and test the app out with all of the new modules.
Demo: Configuring a Module Loader
In order to run the new version of the app that uses modules I need to quickly configure a client-side module loader. In this demo I'll show you a very simple systemjs configuration. I'm going to use the very popular systemjs loader in my app. The first thing I need to do is install it with npm. I'll open the terminal in Visual Studio Code and type npm install systemjs --save. It only takes a second to download into my node_modules folder, and I'll now open the index.html file and configure it. Currently, this file is only configured to download the app.js file that TypeScript outputs to the js directory. That worked fine when I was not using modules and directing TypeScript to output all of the JavaScript to a single file. I'm going to replace this reference to app.js with a reference to system.js in the node_modules/systemjs/dist directory. That's the location of the systemjs code that will take care of loading the modules as they're needed. I'll then use a code snippet to paste in a very simple systemjs configuration. There are lots of ways to configure systemjs, but I'm going to keep things as simple as I can here. If you plan to use systemjs in one of your projects I would recommend checking out some of the other Pluralsight courses that cover it in detail. Because my configuration is really just calls to a couple of the systemjs functions, I'm going to wrap it inside script tags. I first call the config function on the main system object, and pass it an object literal that basically just tells it that my modules are in the commonjs format, and that the default extension for the source files is js. I then call the import function and pass it the starting point for the app, which is the app.js file inside the js directory. I don't have to reference each of my JavaScript files or configure each of them with systemjs. The loader will initially download app.js, and will then download additional modules as it sees them being imported in other files it retrieves. This is all I need right now for my super simple configuration. I'll open the terminal again, and start up my web server with the command npm start. I'll now hop over to my browser and go to localhost:8080. The app loads up with no trouble. I'll now open the developer tools, so we can see how the modules are being loaded. I'm on the network tab, and I'll refresh the page, so we can see each file show up here. The interesting thing to note is that the systemjs file I referenced in the index.html file was downloaded, and the initiator of that download was the index file. A little further down you can see that app.js and then all of the other modules in the app were downloaded, but the initiator for them is listed as fetch.js, which is included with systemjs. I hope this demonstrates that once you configure a module loader it will handle downloading your modules as they're needed, and you don't need to reference all of your individual files. Okay, I'll now close this and play a quick game just to make sure everything is still working after refactoring the app to use modules. I'll use four as my factor this time, and just do three quick problems. I'll quickly answer them without the use of a calculator, and it looks like I again, got them all correct, and everything is working as expected.
Summary
In this module of the course I took a fully functional TypeScript app and refactored it to use modules. I think modules are a great way to organize your code, and I hope I've convinced you to give them a try in your own apps. They let you group chunks of similar functionality together, which lets you think about your apps at a higher level of abstraction. They also have a very simple syntax that is flexible enough to let you only import or export the items you're interested in and, at the same time, give them aliases that make sense in the context in which they'll be used. You also saw in this module several techniques and strategies for configuring how the TypeScript compiler resolves the modules you import in your application. Again, the flexibility of TypeScript means you can almost certainly find a strategy that will work for you and your app. In the next course module I'll cover type definition files, and demonstrate how they give you the ultimate flexibility. The flexibility to include almost any JavaScript library or framework in your app, and get strong typing support and amazing code completion help for it in your editor.
Being More Productive with Type Declaration Files
Introduction and Overview
Welcome back. In this short final module of the course I'm going to cover type declaration files and show you how they allow you to take advantage of thousands of existing JavaScript libraries in your TypeScript projects, and help you write better code faster. This module will be all about type declaration files, but I hope to answer three important questions you likely have about them. What are they, where do I get them, and how do I use them? Let's start with what they are.
What Are Type Declaration Files?
Type declaration files are often referred to as type definition files or just type libraries. They're really just wrappers for existing JavaScript libraries. The goal of a type declaration file is to declare types for the variables, functions, objects, and other constructs in the library that match the intended use of those items. This allows the TypeScript compiler to make sure you're using the library correctly. This could include referencing the correct properties on an object or passing the expected number and type of parameters to a function. These are common runtime problems when working with JavaScript libraries directly, but compiling your code with a type declaration file means you can find those problems at compile time. Declaration files don't replace the JavaScript libraries themselves. They're just a development-time tool to assist the compiler. You'll still need to install and distribute the actual JavaScript library with the rest of your application. Declaration files are usually easy to spot on the file system because rather than the standard ts extension they end with d.ts. As I mentioned before, they're available for just about any popular JavaScript library you can imagine. Any time you decide you need to include a JavaScript library in your app you should always take a minute to see if there's a declaration file available for it. That leads right into my next topic, which is where to find type declaration files.
DefinitelyTyped
The best way to obtain a type declaration file is to find it included with the JavaScript library you want to use. There are lots of libraries that include declaration files for TypeScript developers. Unfortunately, there are still many many libraries that don't. Thankfully, however, there's a huge repository of type declaration files created by the JavaScript and TypeScript communities called Definitely Typed. It's a GitHub repository with declaration files for just about any library you can imagine. The creators of the files send pull requests to the Definitely Typed repository to provide updates to the declarations. It's important to note, though, that the declaration files are often maintained independently of their related JavaScript libraries, often by different people. This means that you may find cases where the declaration file gets slightly out of sync with the library itself. You can go to the GitHub repository directly and download individual files, but Definitely Typed is also the source for several installation utilities that make it much easier to install the files you need. I'll talk about those next.
Installing Type Declaration Files
The first tool developed to help TypeScript developers find and install declaration files was called tsd. It was a command line tool that used the Definitely Typed repository as its source. It was later deprecated in favor of another tool named typings. It improved on some of the shortcoming with tsd and also uses Definitely typed as the source for most of the declaration files it installs. However, beginning with TypeScript 2.0 the recommended tool for installing declaration files is npm, the Node Package Manager. I think there were several reasons for the shift to using npm, but among the most important was to allow developers to install declaration files with a tool they were already using. Most JavaScript and TypeScript developers are already familiar with npm and use it on a daily basis, so it made sense to use it and eliminate the need to use tools specific to declaration files. Definitely Typed is still the source for the files installed with npm. Microsoft developed a program that automatically takes files updated in the Definitely Typed repository and moves them to a special npm organization named types. Let's now jump into a demo, and I'll show you how to install a type declaration file with npm and use it in the demo app.
Demo: Installing and Using a Type Declaration File
In this demo I'm going to use npm to install a type declaration file in the MultiMath app. I'll also show you how to use it, and the nice editor support it enables. Before I show you how to install a type declaration file I want to show you an easy way to search for the files that are available. This is a very simple web page that lets you search for the declaration files you can install with npm. You can see the URL for this page here in my browser, microsoft.github.io/TypeSearch/. I'll show you a couple of quick searches. I'll first type jQuery in the box, and it quickly searches with each keystroke, ultimately showing me several different files that include jQuery in the description. I want to add the popular utility library named lodash to the MultiMath app, so I'll search for it next. The first result is the one I want, so I'll press Enter, and it takes me to the npm page for this package. Notice that the lodash declaration file is part of the types organization in npm. When you install a type declaration from npm they will all begin with @types followed by a forward slash and the name of the declaration file, which usually matches the name of the corresponding JavaScript library. You can see here the exact syntax for installing the lodash file and saving a reference to it in our package.json file. I'll now go back to Visual Studio Code and use the built-in terminal to install the file. Before I install the type declaration file I want to install the lodash library itself. To do that I'll just type npm install --save lodash. Now that I've got it I'm ready to install the type declaration file. I'll use the same command we saw on the npm site; npm install --save @types/lodash. Notice that the version number for the library and the declaration files are slightly different. Remember that type declaration files are often maintained by different people than the library itself, and may not always be 100% in sync. In this case, the versions are close enough that I'm not really concerned it's going to cause me any problems. Okay, I'll now close the terminal and give you a quick peek inside my node modules folder in the project. You can see that I now have a folder for lodash that will contain the actual JavaScript library, and up a little higher in the list I have another folder named @types that has a lodash folder inside it. That folder contains the type declaration files that were installed separately. I'm now ready to add a small bit of lodash to my app. I'll do that in the scoreboard.ts file. The first thing I need to do here is import lodash, so I can use it in this module. I'll add an import statement at the top of the file that imports the entire module and aliases it to the underscore character, which looks a bit like a low dash. I'll use a relative reference to the lodash library, which will cause the compiler to look for it in the node_modules folder, as I described in the last course module. With that import in place I'm ready to call the functions available in the library. I'm going to keep this very simple. I'll come down to the addResult method and add a couple of lines that will log the new result to the browser's console. I'll declare a new variable that will store an uppercase version of the player name. I'm going to use a lodash function to convert the name to uppercase. I type the underscore character, followed by a period, and you can see that Visual Studio Code is now giving me code completion help for all of the lodash functions. The compiler is getting all of this information from the type declaration file. I'll start typing the name of the uppercase function, and it quickly filters the list to the function I want. You can then see type information about the function. It takes an optional string parameter and returns a string. That type information allows the compiler to generate an error if I try to call it with the wrong type. I'll pass it the playerName property of the newResult object. I'll then use a template string to log the uppercase version of the playerName and the new score to the console. That's the only code change I'm going to make right now. Before I can run this I need to change my systemjs configuration, and tell it where it can find the lodash JavaScript file that it will need to download to the browser. I'll do that back in index.html. I'll use a code snippet to paste in an additional property on the configuration object. This line just tells systemjs that it can find the module named lodash at the following location inside the node_modules folder. With that final change in place I'll press Ctrl+Shift+B to start my build task, and then hop over to the terminal and start the web server with the command, npm start. I'll now go back to my browser and open the app at localhost:8080. I'll open the console so we can see the new output, and then I'll show off my considerable math skills for the final time, this time with factor six. I'll quickly answer the problems and click the Calculate Score button. I again got them all correct, and you can see the new output with my uppercase name in the console. Everything is working as expected.
Summary
The JavaScript ecosystem is very rich. There are mature, stable libraries available to do lots of common tasks. Being able to tap into that ecosystem, and use those libraries together with strong typing support can provide a huge productivity boost. Adding type declaration files to your project helps you write code faster. You'll spend less time looking up function and property names and more time implementing features. You'll also find errors faster. The TypeScript compiler will alert you immediately if you pass a string to a JavaScript function expecting a number. You won't have to wait for a frustrated user to report the error. All of this means that you'll provide value to your users faster, and that's really what it's all about, so definitely check to see if there's a declaration file available for the next JavaScript library you pull into your TypeScript project. Okay, that brings us to the end of the course. I hope you've enjoyed it and are eager to start building your next app with TypeScript. If you've made it this far you have all of the skills you need to be productive with the language. If you're ready to dive deeper into some of the topics I've covered or want to take your skills to the next level I've got two more TypeScript courses you should check out. TypeScript In-depth goes deeper into some of the topics I've presented in this course, and also covers new topics, such as generics, namespaces, abstract classes, and lots more. Advanced TypeScript covers a completely different list of topics, including intersection types, decorators, and asynchronous code. If you've made it through this course you're ready to continue learning TypeScript with either of these two courses. Finally, I want to say thanks for watching. I really enjoy making these courses, and I hope you find them helpful.
Course author
Brice Wilson
Brice has been a professional developer for over 20 years and loves to experiment with new tools and technologies. Web development and native iOS apps currently occupy most of his time.
Course info
LevelBeginner
Rating
(153)
My rating
Duration3h 8m
Released18 May 2017
Share course