What do you want to learn?
Leverged
jhuang@tampa.cgsinc.com
Skip to main content
Pluralsight uses cookies.Learn more about your privacy
Using ES6 with TypeScript
by Steve Ognibene
This course teaches how to use the new syntax features of ES6 today, with TypeScript as a transpiler.
Start CourseBookmarkAdd to Channel
Table of contents
Description
Transcript
Exercise files
Discussion
Learning Check
Recommended
Course Overview
Course Overview
Hi, my name is Steve Ognibene and welcome to Using ES6 with TypeScript. This course focuses on the new syntax introduced in ECMAScript 2015, the sixth official edition of JavaScript, still most commonly referred to by the shorthand ES6. While we'll be using TypeScript as our transpiler, the vast majority of our time will be spent covering topics that are applicable in any ES6 environment. So this is very much a JavaScript course. ES6 is an enormous release. It contains new syntax features, many new built-in objects, enhancements to existing built-in objects, and even several behind-the-scenes engine specifications. The focus of this course will be on the ES6 syntax features. To use the new ES6 syntax features today it's possible to use a transpiler. A transpiler takes code in one form and transforms it to compatible code in a different form. Throughout this course we'll use TypeScript to transpile our ES6-style JavaScript code into an ES5 compatible form. If fact, most of the code will even work in Legacy ES3 environments. We'll start off will a module dedicated to the new general syntax features of ES6. I'll talk about the new keywords let and const, and contrast them will how the var keyword works today, including the difference between clock scoping and function scoping. We'll cover arrow functions and the lexical binding of this. We'll talk about de-structuring objects and arrays, including how to use the structuring and method signatures. We'll cover the new object literal short-hand and also the new rest and spread operators. We'll discuss string templates including tagging, and finally we'll wrap up the syntax module with an overview of for/of loops. After that, the next course module will cover the new ES6 module syntax. First, I'll cover how to export and import objects, and then I'll give a practical demonstration of how to get ES6-style module syntax working with three popular module loaders that work in today's JavaScript environments, RequireJS, SystemJS, and the common JS module loader built into NodeJS. The last course module will cover ES6 classes, including constructor functions, instance properties and methods, static properties and methods, and inheritance.
Setting up Atom for TypeScript
Throughout this course I'm going to use the Atom editor with the atom-typescript plugin, as well as NodeJS. You can use whatever editor makes you happy, but it helps if that the editor integrates with the TypeScript language service. If you want to use Atom, install it from atom.io. To get the latest NodeJS release, go to nodejs.org. Once you launch Atom you'll need the Atom TypeScript plugin. Open Settings in the File menu and then click the Install tab, not the Packages tab. Search for atom-typescript. The plugin you want if from TypeStrong. Just be aware that atom-typescript has a lot sub-packages. So it might take several minutes to install, even with a fast computer and internet connection. You'll see a popup like this when it's done. By the way, if you're interested to get an Atom theme similar to the Visual Studio blue theme, click the Themes button in the search box and search for redmond, that's the UI and syntax theme I use in this course. Atom works best with TypeScript when it's pointed at a folder. I'll click File, Open Folder, and create a new folder called Hello World and select it. Now I can right-click and create a new file called helloworld.ts. If atom-typescript installed correctly you'll see this bar on the bottom when you open a TypeScript file. It's warning us that we don't yet have a tsconfig.json file anywhere under the Hello World folder. Tsconfig.json is required by the TypeScript language service through _____ compilation. We can easily create one with Atom by accessing the Command Pallet, Ctrl+Shift+P, and type in tsconfig. I'll click that and we can see that atom-typescript has created a default tsconfig.json file for us and now our error's gone away. I'll type console.log, Hello world, and when I save TypeScript will admit helloworld.js. I can press Ctrl+Shift+M to bring up the Preview window so we can see our TypeScript on the left, and our admitted JavaScript on the right, not too different, huh? Atom-typescript always ships with a very recent TypeScript language service, but if you want to use a specific version it's possible to select one. First, download the version you want from npm to a new folder by running npm install typescript@ version, such as typescript@1.7.5, then in Atom under File, Settings, Packages, click Settings next to atom-typescript. In the Settings section specify the full path to the typescriptservice.js file that you just downloaded from npm. One last thing I need to mention is how to set up the sample code. If you want to run the sample code be sure to open the folder in your NodeJS console and run npm install first. That'll download all the libraries used in this course including the happy web server from npm. Additional details are in the readme text file included with each code sample. So with all of that out of the way let's begin our exploration of using ES6 with TypeScript. By the end of this next module you'll be ready to use the new ES6 general syntax features in your JavaScript applications.
New ES6 Syntax
Introduction to Transpiling
Welcome back to Using ES6 with TypeScript. In this module we're going to cover much of the new ES6 syntax. Everything we cover will work as in future browsers and JavaScript environments, but to get this code working in today's environments we'll have to use a transpiler. As we discussed in the last module a transpiler takes the code you wrote and transforms it to some other code that actually runs. We'll use TypeScript to transpile our ES6-style code to ES5 style. With a transpiler all the features we discuss in this module can be used today in production with NodeJS or with any ES5 browser. In fact most of the code can even be used in ES3 browsers, like IE8 and below. I'll highlight it whenever something can't be. The content in this course assumes that you have at least TypeScript 1.5, I'll let you know whenever a newer TypeScript version is required to get something to work right. If TypeScript can't transpile an ES6 feature it'll usually give an error. In that case we can still TypeScript to transpile our ES6-style TypeScript to ES6-style JavaScript, and then we can use a second transpiler, in this case Babel, to transpile the ES6 JavaScript to ES5. This may sound complicated, but it's easy to set up with atom-typescript and we'll cover when you might need to do that and how. The first new feature I want to cover is the let keyword. This is a new keyword for declaring variables and it works somewhat differently than var. To help demonstrate the difference let's quickly review two distinctive characteristics of var in JavaScript, hoisting and functional scope.
Hoisting and Functional Scope with var
Var in JavaScript works a bit differently than similar keywords in other languages. For example, let's consider this JavaScript function. If the name parameter equals Amy it appears like we're declaring a variable called fav, and if the name isn't Amy it looks like we're declaring a different variable called fav. This might be an issue in other languages, but in JavaScript these two fav variables are actually the same. Anytime var is used in JavaScript something called hoisting happens. Behind the scenes the JavaScript code is rewritten so that all variable declarations using the var keyword in a function are hoisted to the top of that function. This means that in actuality the code we wrote looks like this when it executes. One declaration of fav is hoisted to the top of the function and any other declarations of the fav are ignored. Because the var keywords are ignored after hoisting happens those lines act as normal assignments. We can prove this behavior by doing something really unusual. Let's move the declaration of fav down to the bottom of the function. I don't recommend actually doing this because it's very confusing, but because of hoisting, this code will still work as expected even though it looks like the declaration of fav is blocked by the return statement and will never be reached. The next important thing to understand about var is scope. The var keyword uses functional scope. We already saw that uses of the var keyword inside a function get hoisted to the top of that function. Uses of var outside a function are considered part of the global scope and get hoisted to the top of your entire program. This global scope is shared even across different scripts, and re-declaring a global variable with var has no effect for the same reason that re-declaring a functions variable with var has no effect. The declarations are essentially consolidated when they're hoisted. For example, if I have Script1.js and Script2.js, and both declare a variable called person, using the var keyword, JavaScript treats this as a single variable in the global scope. The value of person at any given time depends on the order in which these scripts are referenced. So to review, the first important thing to remember about the var keyword in JavaScript is that it's variables get hoisted, and the second important thing to remember is that it uses functional scoping. This isn't anything new, it's how var has worked in JavaScript all along.
Introduction to let
Now that we've reviewed var, let's talk about the new ES6 keyword let. As we said, var hoists and uses function scope. Let is different than var because variables declared with let are not hoisted and they use block-scope. So what does that mean? Well, let's go back to our original example, but change each var keyword to let. Now TypeScript gives us an error, Cannot find name fav. What happened? Well firstly the let keyword in ES6 is block-scoped. Blocks can general by thought of the closest containing set of curly braces. This'll be familiar to those who also know languages like C, C#, or Java. With let the first fav is a variable that is only scoped to these curly braces, and the second fav is a variable that is only scoped to these other curly braces. Because it's outside of the scope of the fav/let declarations, the final fav here doesn't reference anything at all, which is why TypeScript gives us an error. So while the var keyword gives us function scope, the let keyword gives us block-scope. What happens if we fix this by adding a new line to declare the variable up top? Let, fav. Well now this is even worse because we've got three versions of fav, the one we just made in the block-scope bounded by the curly braces on the function, and a separate one in each of the if branches. As written this function will always return undefined because the values are only set on the variable scoped to the branches, not the one declared at the top of the function, which actually gets returned. To get this to work correctly we need to remove the let keyword in each branch. Now there's one fav variable that exists throughout the function and it'll work as intended. The second major difference from var is that the let keyword doesn't hoist. To see this in action let's recreate our eyeball example where we declared the variable after the return statement. Because of hoisting this code worked fine when using var. But what happens when we change it to use let? Now TypeScript gives us an error every time we reference fav. Block-scoped variable fav used before its declaration. The reason we get this error is because variables declared with let are not hoisted. The area within the block above the let declaration is called the temporal dead-zone. That sounds like the name of a heavy metal band, but it's really just a fancy term for saying, you can't use a variable declared with let until after the let keyword has been hit. Because of hoisting it's okay to re-declare a variable with the var keyword inside a scope. The var keywords are hoisted to the top of the scope and the lines are treated as assignments. However, with let re-declaring a variable in the same scope becomes a duplicate identifier error. This can be useful to identify possible bugs in your program.
Using let in a for Loop
There's a special case that I want to highlight where let bends the rules of block scoping, and that's when let is used to declare the iterator variable in a for loop. Consider this function called printNumbers. It's designed to naively log out numbers from 1 to whatever number's passed in as the next parameter. What happens if we add a line console.log i outside the loop? If we didn't understand hoisting and functional scope in JavaScript you might expect that this would be an error. However, now we know that when var is used the declaration of i is hoisted to the top of the function, so i remains in scope throughout the entire function. If we call printNumbers with a max of 2 this extra console.log statement would log 3. That's the first number that meant the for loops exit condition. And for loop iterators declared with var are not reset when the loop exits. If we were to duplicate this loop and reuse i as the iterator our code now uses the same i variable in both loops, which likely isn't always intended. The variable i is reset at the top of each loop so the iterations will work as expected. But if we were using i for something else inside this function, its previous value would be lost. The lines with var are treated as an assignment to the same function-scoped i variable in both loops, they don't create their own new variable. Let's switch these var keywords to let. As I said before, ES6 slightly bends the rules of block-scoping when let is used to initialize a for loop. Even though it's technically outside the curly braces the iterator of a for loop declared with let is scoped to be inside the braces. With this code a separate i variable is now scoped within each for loop only, no i variable exists in the middle anymore, which is why TypeScript now shows an error for our extra console.log statement.
Scope Per Iteration Behavior of let
ES6 has another very interestring behavior when let is used to initialize a for loop beyond just keeping the iterator scoped to the loops curly braces. Using let creates a separate copy of the variable for each iteration through the for loop that gets closed over. To demonstrate why this'll be useful, I'll open up my Node.js command prompt in the sample code folder of this course. There I'll type node server.js to launch the demo of server. Once that's running I'll open up localhost:3000 in my browser and click For loop closure with let. On this page we see some buttons, they were created in a for loop that used var to declare the iterator. Notice that when I click these buttons they all claim to be buttons they all claim to be button 4, why is that? Well, let's look at the code. The buttonsWithVar function has a for loop with an iterator i declared using the var keyword. A handler function for the button's onclick event is created here. It's calling alert with the value of i. Based on what we know about the var keyword, it's not surprising that the i here is the same i being declared in the for loop. However, what is often surprising is that each iteration through the for loop refers to this same i variable. A copy of its value is never made and so each click handler dutifully alerts this is button 4. Four is the first value of i that met the loops exit condition, and that value is preserved in a closed scope shared by each click handler. So how do we fix this? To get this to work as intended in ES5 JavaScript we have to manually create a new scope that will close over the value of i in each iteration. That way each handler can display the appropriate message. A common way is to wrap the code that creates the handler in an immediately invoked function expression, or IIFE that will close over its argument values. An immediately invoked function expression is a JavaScript term that very much means what it says. It's an expression that creates a function that is immediately invoked. Immediately invoked function expressions, or IIFEs, are handy in JavaScript since they serve as a convenient way to isolate scope. Let's copy buttonsWithVar and rename it to buttonsWithVarAndIIFE. To get it working we'll comment out the old value of onclick and set it to a stub IIFE. Open parentheses function, open parentheses, close parentheses, open curly brace, close curly brace, open parentheses, close parentheses, close parentheses. So what's this doing? Well, it looks like we're declaring a function just like how you'd declare any other function. Okay, and what's this? What does it mean when you have an open parentheses and a closed parentheses after a function? Well it means you're calling that function. Great, so it means that this whole thing is a function declaration that we're immediately calling. It's an expression that creates a function that is immediately invoked, an immediately invoked function expression, perfect. Notice how TypeScript gives us an error, Type void is not assignable to a function that takes a mouse event and returns any. Okay, I guess we have some more work to do. We want to preserve the value of i at the time this IIFE is invoked, that means we have to pass it in. Just like with any function you pass the parameters and parentheses at the end. Now TypeScript tells us Supplied parameters do not match any signature of the call target. This means that if we're passing in a parameter our function really ought to expect it as an argument. I'll add an argument i here just where you'd normally put an argument declaration. Alright, we're back to the Type void is not assignable error, which is correct. Our IIFE isn't returning anything at the moment, which means it's void. The on-click event handler must be a function, so let's return the one we had from before. (Working) And that's it. The important thing to remember is that this i is the same as this i. However, because this i is being passed as a function argument while the loop is running, the i here represents the value of i at that time. In fact, you don't have to keep this called i, our intent might be clearer if we renamed it. How about buttonNumber? Then we'd have to change the alert to use the our buttonNumber argument too. So we're passing in the value of i to this function, which is immediately invoked. ButtonNumber gets the value of i at that time, and the click handler that it returns will use that value of buttonNumber. Each run through the loop will have a separate function that retains access to a separate closed over scope that includes a different value for buttonNumber. Just to confirm let's try it out on the website. To make this work I'll uncomment the call to buttons with var and IIFE at the top of the file, save, and refresh the browser. We can see that the second set of buttons now works as expected. That seems like a lot of work. I'm pleased to say that in ES6 we can just use let to declare our for loop iterator and this behavior happens automatically for us. When using let with a for loop, each iteration gets a separate copy of the iterator variable in its own scope, which eliminate the need to hand code an IIFE. Let's copy the buttons with var functions again and rename it to buttonsWithLet. Then I'll just change var to let. We can see that TypeScript give us an error. Loop contains a block-scoped variable referenced by a function in the loop. When TypeScript gives us an error it's usually a good thing because we know right away that there might a problem. In this case, the version of TypeScript language service shown in this clip doesn't support the new ES6 _____ scope or iteration feature of for loops declared with let. Transpilation support for that particular ES6 feature was added in TypeScript 1.8, which is in Beta as of early 2016. So if you're able to use TypeScript 1.8 or higher in your environment you'd be done now, if you don't have TypeScript 1.8 you're free to use the IIFE pattern we discussed or you can use a second-stage transpiler like Babel to emit the IIFE for you. Babel is very easy to use as a second-stage transpiler with atom-typescript and it provide a handy backup plan just in case TypeScript doesn't transpile something as you expected. Let's talk about Babel as a second-stage transpiler, assuming we're using a TypeScript version before our 1.8. So what the heck is a second-stage transpiler? Well, it's a just a way to do the transpilation in two stages. Typically in TypeScript we write our code in ES6 file with some type annotations. The code we write runs through the TypeScript compiler and emits valid ES5 JavaScript. In this case though TypeScript doesn't know how to properly emit the scope per iteration behavior of let in a for loop, and it's warned us of that fact. Since an ES6 environment could just handle that natively, TypeScript is happy to emit our let/for loop code as ES6, the let gets preserved in the emitted code. Once we switch TypeScript to ES6 mode we still need to somehow get from ES6 to ES5 though to run in today's JavaScript environments. A tool that is specialized for ES6 to ES5 transpilation is Babel, and we can use that take the ES6 emitted by TypeScript and transpile it to ES5. An atom-typescript support for Apple's built in. In other environments you may have to set up a post-build step on the command line. Let's set that up now. I'll open the tsconfig.json file on the root of our project and set the target from ES5 to ES6. That means the TypeScript will not have to do any transpilation work for features that work in an ES6 environment. In our case, it'll just do type erasure and transpile the module declaration in the emitted JS. Then I'll add a new key called externalTranspiler and set it to babel. That's it. By the way these are case-sensitive. Now when I switch back to ForLoopClosure TypeScript file the error's gone away. At the top of the file I'll just uncomment the call to buttonsWithLet and save everything. Now when I refresh the page in our browser the third set of buttons that use let to declare the for loop iterator works as expected. Now let's move on and talk about const.
Introduction to const
The const keyword in ES6 is very similar to let. Variables declared with const are also block-scoped and aren't hoisted. The main difference is that the value of a const must be set when it is declared and it can't be changed later in the scope. Let's take a look at an example. In this TypeScript file let myvar is fine. If I change it to const myvar it's an error. In ES6 you have to provide a value for a const when it's declared. Const in ES6 behaves a bit differently than in some other languages though. For example, an ES6 const can be instantiated to the return value of a function or an expression, it doesn't have to be a true constant like a string literal or an number. This means that code like const startTime = Date.now or const answer = confirm Are you sure? works perfectly fine. Const effectively prevents reassigning a new value even when it's used to declare an object. However, there is a wrinkle. Here I'll create an object literal. If I try to set simpleLiteral to a new value TypeScript shows me an error. However, if I try to set the value on a property on simpleLiteral this works fine. So it's important to remember that the value of an ES6 const itself must remain constant, but the value of any of its properties may be changed. Does that mean that there's no way to have the properties of an object be constant via the const keyword? Well, it's possible to achieve this using a TypeScript namespace. Namespace is a new keyword that was introduced in TypeScript 1.5. It replaces the use of the module keyword when using what was previously called internal modules. Namespaces are a TypeScript only concept and not part of the ES6 standard. Let's do a quick test of how properties declared with const work in a TypeScript namespace. I'll create a new namespace called AtomicNumbers. Then I'll add hydrogen and helium to it as exported constants. The export keyword means that this property is available outside the namespace. If I try to re-declare hydrogen, even if I set it to the same value, I get the error, Cannot re-declare block-scoped variable H. If I simply try to change its value I get Left-hand of assignment expression cannot be a constant. I even get his error if I try to modify the namespace from outside, AtomicNumbers.H = 3. If I were to change the keyword on H to let or var this line would work. So this might make you wonder if const works with modules does it also work with ES6 classes? The short answer is no. Early on in the proposal process there were discussions about allowing that, but in TypeScript and the final version of ES6 the const keyword can't be used to declare class members.
When to Use let and const?
So when should you actually use let and const with ES6 and TypeScript 1.6? I think that using the new keywords where appropriate can help to clarify the intended purpose and scope of your variables. As a bonus it's also nice to eliminate the need for hand-coding, IIFEs, and for loops. But as we saw, that feature still requires a second-stage transpiler like Babel if using a version of TypeScript prior to 1.8. If you're looking for a good recommendation I think a fair one is that first you should try to use var when you intend for a variable to be resistant to re-declaration. Mostly this will be for intentionally global variables that are used across files, that scenario should be pretty rare. The second case where you may still wish to var is inside a function if the block-scoping behavior of const or let creates an awkward situation. We'll see an example of this with a try/catch block in just a moment. Pretty much you should use let or const at all other times. The tighter scope of let and const declared variables will hopefully lead to more correct programs that are easier to understand. In addition you should try to use const over let as much as possible. You'd be surprised how often variable don't need to be updated after being initialized, particularly object literals. Don't forget that it's fine to update the properties on a const variable, it's just the variable itself that can't be changed. A cool thing about using TypeScript is that it can tell you instantly if a variable you've declared with const is ever used in the left-hand side of an expression. For these variables you just have to use let instead. To see let and const in action I'm going to open up the coinCounter project in our sample code. And I'll look at coinCounterViewModel. CoinCounter is the program I convert to TypeScript in my other Pluralsight course, Practical TypeScript Migration. This is the main view model for our game. Let's see if we can refactor uses of var to let or const. On line 8 I'm declaring highScoreIndex. It doesn't seem like we're setting it again so we should be safe using const. On line 9 I'm using var to declare a message. We modify the value of message on lines 11 and 14 also so it can't be const, but let's try it anyway. Sure enough, TypeScript tells us that the Left-hand side of assignment expression cannot be a constant. Okay TypeScript, we'll use let instead. I refactored the rest of coinCounterViewModel off camera to change all uses of var to let or const. In 12 uses of var I was able to convert 9 to use const and only 3 had to be let. None were required to remain as var. Even if you're excited about let and const, something I can't recommend doing is a global find and replace from var to let in your code base. This will almost certainly cause problems where your code is perhaps unintentionally relying on hoisting. Even in the simple examples that I showed here we saw that happen when var was used twice within the same variable in a function. Another scenario that you might see is when var is used to declare a variable inside a try/catch block that is then used outside the try/catch block. Here's an example from the open source TypeScript build system grunt-ts. By the way grunt-ts isn't included for the sample code download for this course, but it's available to the public under the MIT license on GitHub at this URL. I'm inside tsconfig.ts, which is inside the tasks-modules folder. This code declares a variable called projectSpec with type ITSConfigFile, and then sets it value by parsing some JSON. The code runs inside a try/catch block that will exit the function if an exception occurs when parsing projectFile text content. If the parsing doesn't raise an exception these other two lines will run. This is an interestring scenario. The value of projectSpec is never changed other than when it's set on line 84. However, if we change it to const we can see that lines 89 and 90 now have errors. This is because const uses block-scoping and now projectSpec is scoped to these curly braces. We could move lines 89 and 90 up into the try/catch block, but then any exceptions thrown by the applyCompilerOptions or resolve_out_and_outDir functions would be incorrectly indicated as JSON parsing errors in the catch block. So we're effectively prevented from using a const here because of our exception handling. We could try using let, but then we'd have the same scoping problems as const. The only way to fix this is to use let to declare the variable outside the try/catch block and then to merely set the value inside the block. I'm not certain that this new code is actually better than just using var. I suppose it's good because the temporal dead-zone at least prevents us from accidently using projectSpec above this line, but now it's an extra line of code to maintain. In this case your team will have to make a decision as to if using let actually gives you benefit versus just using var inside the try/catch. So as you work on your code base feel free to change var to let or const in whatever are you're working on as long as it makes sense. I've found that I can use const most of the time, let a bit less often, and that generally I don't need to fall back on var, but there are times it makes sense. Don't feel any pressure like you need to abandon var, var is not going away, and it's still a perfectly valid way to declare variable in ES6. You're not doing anything wrong by keeping it around where appropriate, especially in working code that you haven't touched recently, or if using let or const don't feel right in a given scenario.
ES6 Arrow Functions
In this clip we're going to talk about ES6 arrow functions, which are a new JavaScript feature in ES6. They provide a short-hand syntax for declaring functions and simplify the behavior of the this keyword in JavaScript. Arrow functions have been supported in TypeScript since the original public alpha release in 2012. Let's take a look at a simple function and re-implement it as an arrow function. Here I've got a function called greetRegular. When I pass in a value like Steve for the name parameter it will return Hello Steve. I want to create a new function using the arrow style called greetArrow. First I'll have to declare a variable to hold it, I'll use const instead of let since I don't expect that this function will change while it's in scope. Const, greetArrow, then I could put = and open parentheses for the parameters list. I'll add name and close the parentheses. You may notice that this is the same as the arguments part of the regular function declaration. Now comes the arrow part, which is an equal sign and greater than. Arrow functions are sometimes called fat arrow functions, or lambda functions, but the official ES6 term is arrow function. Now that I've got the arrow I can put the curly braces and specify the same function body as before, return Hello + name, great. You may notice that it doesn't look like we've shortened things much versus a regular function declaration. Well the good news is that with arrow functions we actually omit a lot of this code. I'll copy our full-blown arrow function and create a greetMin function. First of all for arrow functions with exactly one argument you can omit the parentheses here. Also if you're function is a one-liner you can even omit the curly braces and the return keyword. That leaves our code a lot more terse. GreetArrow and greetMin have exactly the same behavior. I should mention that if you're using TypeScript with the No Implicit Any feature activated you'll generally have to specify the types of function parameters. In this case you can omit the parentheses around the parameters list, but typing still works fine if you omit the curly braces and the return statement on a one-liner. I'll strongly type the name parameter by putting parentheses back and typing : string. Also when adding a function to a scope I highly recommend using let or const to declare it instead of var. As we know var declarations get hoisted to the top of the containing function, however, the variable's value will remain undefined until the line that sets it has been reached. To demonstrate why this could be a problem I'll change our greetMin function to a var declaration, then on the line above I'll write console.log, greetMin, Amy. Notice how there's no error. At runtime though this code would fail because we're calling greetMin on line 13, but it doesn't get initialized as a function until line 15, greetMin is still undefined when we're calling it. If I change greetMin back to const or let TypeScript is able to take advantage of its knowledge of the temporal dead-zone, and it warns us that greetMin is being used before its declaration, isn't that great? We would have hit that error at runtime in regular JavaScript. If I move the call to below the instantiation TypeScript will now be happy. A very important point about arrow functions is how they work with the JavaScript this keyword. In traditional-style JavaScript functions the thing that this references depends on how you've called the function. Sometimes this references the containing function, sometimes this references the global namespace, and sometimes this references something else altogether. It's difficult to know in advanced because it all depends on how the calling code was implemented. Arrow functions are different because this always refers to the containing code. The fancy term for this is lexical binding. In addition, if one or more arrow functions are declared within another arrow function, this in the inner-arrow functions will be the same as whatever this is in the outmost-arrow function. I talk about this subject in depth in my other Pluralsight course Practical TypeScript Migration. The relevant section is Lambdas and how this works in TypeScript. There are two other small details that I should mention about arrow functions. The first is that in ES6 there's not a built-in arguments object available inside an arrow function like there is in a regular function. If you need to iterate over the arguments passed to an arrow function you'll have to use a rest parameter. We'll talk more about those in a later clip, too. The second thing is that ES6 arrow functions aren't new-able. A common pattern in JavaScript is to use a function as a class constructor by calling it along with a new keyword. That behavior is supported in TypeScript and you can see a fully-fleshed out example of the constructor pattern in the coinCounter sample code file, GameClock.ts. The only thing is that if you want to use a constructor pattern with your own function you can't use an arrow function for the constructor itself because this will wind up referring to the functions lexical container. Instead you should use a standard function so that this will be set to reference the object you're actually constructing. Unfortunately TypeScript doesn't specifically give an error when calling new on an arrow function, but basically the code just doesn't work at runtime so be sure to use a regular function for your constructors.
Destructuring Objects
One of the coolest new features introduced in ES6 is destructuring. Destructuring provides a way to break up objects and arrays into component variables using a very terse syntax. Using this feature sometime feels a bit like magic because it's not uncommon for most of the code in a destructuring assignment statement to appear on the left-hand side of the equal sign, let's take a look. Suppose I have a US mailing address object, like this, that acts as each of the properties of addressData1 without resorting to something like using with, I have to type addressData1.property name. It's not horrible, but it's a lot of code, especially if that property's used often. One way to work around this would be to create a different variable, const streetAddress1, and assign it the value of addressData1.streetAddress1. Now my code can be a bit shorter every time I use streetAddress1, but I still have to manually write the code to declare the variables and extract the properties, and honestly that's probably not worth doing if it'll only be used once. Destructuring provides a terse way to break out an objects properties into separate parts. I can type const, open curly brace, streetAddress1, close curly brace, equals addressData1, and ES6 will declare a constant called streetAddress1 and assign the value of addressData1.streetAddress1 to it. I could use var or let, too, but as we discussed earlier it's best to use const whenever possible. If I want to extract more properties of addressData1 I can just provide them and set the curly braces separated by commas, streetAddress2, city, state, zip, and country. On the next line you can see that if I type streetAddress1. atom-typescript knows that it's a string and it gives the appropriate string members. I can also hover my mouse pointer over it, pretty cool. A nice feature of destructuring is the ability to extract an object's property into a variable of a different name. The way you do that is with the colon character. Beware though that since it uses a colon this looks a bit like a TypeScript type declaration. Inside a destructuring block the colon has a different meaning though, and it's only used to alias property names. If I wanted to take the streetAddress1 property from addressData1 and assign it to a shorter variable name like street1 I would just put : street1 after the streetAddress1 property, I'll do the same for streetAddress2. The object property names goes on the left and the destination variable name goes on the right. That's kind of opposite of how assignment usually works in JavaScript, but we're on the opposite side of the equal sign anyway, so maybe it'll help to think that everything is mirrored. Now I can console.log street1 and console.log street2 and TypeScript knows that these are strings. What happens if our property on the object is missing? I'll comment out streetAddress2. Now TypeScript gives me an error, the Type has no property streetAddress2 and no string index signature. Well, geez, I'm glad we cleared that up. TypeScript is telling us that our object doesn't supply a streetAddress2 property, even though it's expected. There's two ways to work around this error. By the way, type errors like this are a TypeScript-specific thing and not part of ES6. The TypeScript language service is trying to help to make sure that we really intended to enter a situation where we'll have to make runtime null or undefined checks. The first, and quickest work around, is just to put an any type assertion on addressData1. Unfortunately though, this means that each of the destructured variables will also be of type any now since TypeScript won't use type inference for them anymore because of the assertion. To get this working and keep our variables strongly typed we'll need to specify a type for addressData1. I'll create an interface called USPostalAddress and add the US standards properties to it. Now I can declare addressData1 as an USPostalAddress. Hmmm, I still get an error, but this is a different one, not assignable, streetAddress2 is missing. Well with the way I've declared this interface all properties are mandatory, I can make streetAddress2 optional with a question mark, and now TypeScript is happy again. You'll have to decide with your team how much effort should be spent on strongly typing your code, and also consider if any particular section of code is worth spending the effort on versus tagging something with any and moving on.
Using Defaults and Deep Destructuring
Another feature of destructuring that I want to show is the false. In our code streetAddress2 is now missing. This means that the street2 constant will be undefined. If you'd prefer a value like blank string this can be arranged by adding = blank string on it. When using destructuring defaults are only applied if the property is undefined, null will stay as null. This line's getting a bit long, let me fix the formatting. When using destructuring it's also possible to access deep properties. I'll create a constant called employee with a property called workAddress. Then I'll assign addressData1 to workAddress. On the employee object I'll also create a variable called position, which will have a property called department, which will have a property called name. Let's say we're trying to categorize this employee by department and city. We can write a destructuring statement to get the city and department from the employee constant. I'll start with const, open curly brace, closed curly brace = employee. Then I want to get the city from workAddress so I can type workAddress: open curly, city, close curly. They also want the department name so I'll put comma, position: open/close curly, then department: open/close curly, then name. Actually let's alias name as departmentName with :departmentName. TypeScript is giving us an error, cannot re-declare block-scoped variable city, do you know why? We're actually declaring city up here in our earlier destructuring statement. I could delete that code, but what I'll do temporarily is just wrap this code in curly braces to establish a new scope. Now the city constant here exists only in this block, and the city constant from up there will be in scope elsewhere on the block. Notice that if I put console.log workAddress TypeScript gives us an error because it's not actually created as a variable. Only the innermost items in a destructuring assignment are actually pulled out and assigned to variables, in this case city, which was two levels deep, and name, which was three levels deep. Now this is all kind of neat, but the thing that really knocked me out the first I saw it was that ES6 allows the use of destructuring assignments as function arguments. For example, let's create a categorized employee function. Function categorizeEmployee, open, close, open, close. De-implementation will be return, open curly brace, city: city, departmentName: departmentName. Now, check this out, I'll cut the destructuring assignment statement that we were using before, and paste it in as the method signature. Notice that TypeScript gives us no errors. To call categorizeEmployee I'll just write const category = categorizeEmployee employee and everything works. So what's happening here is that the function is performing the destruction on the argument automatically and the city and department name variables are now scoped to that function only. Pretty cool stuff. All the rules we discussed for destructuring objects into variables and regular code should work the same when destructuring in a method signature. Another cool thing in ES6 is a new short-hand for creating object literals. The property of an object should be the same name as the variable you're assigning to it, you can just put the variable name. So I can get rid of :city and call in departmentName here, and this code will work the same. That isn't specifically related to destructuring, in fact it's kind of more like structuring, but I figured it made sense to mention here. One thing to note is that unfortunately with destructuring we still have the problem of getting an exception when attempting to read a property on undefined. Let's say that employee didn't have a work address. You can see the TypeScript says that the workAddress is missing on the value I'm passing in. If I try to run the emitted in JavaScript we'd actually get the error, cannot read property city of undefined. The way to work around this is by supplying a default. Remember how we did that for street2 by using the equal sign and supplying a value? ES6 destructuring even supports applying defaults for entire objects. Up here, after the expected structure workAddress is specified, I'll write equals and put in an object literal, open curly brace, city: blank string, closed curly brace. Now if workAddress is missing from the parameter pass in the function our default object literal here will be substituted for it, and the value for the city variable will be extracted from that, in this case, blank string. ES6 destructing works with more than just objects, it works with arrays too. In the next clip we'll see that and two new JavaScript operators, spread and rest.
Destructuring Arrays with Rest and Spread
In this clip we're going to have some fun destructuring arrays. Let's say we have an array of names, Alice, Bob, and Charlie. If we want to assign the first item in this array to a variable called firstTraditional we can type const, firstTraditional = names, open bracket 0, close bracket. Now let's see how we can use destructuring instead. You may recall that when we were destructuring objects we surrounded our destructuring assignment with curly braces. Curly braces are often used with objects in JavaScript. The characters most often used with arrays is square brackets, and those are we use to destructure an array. I'll type const, open square bracket, firstDestructure, close square bracket = names, and that's it. The first name in the array, Alice in this case, will now be assigned to firstDestructure. I can add secondDestructure here and it'll get the value Bob. What happens if the array's empty? JavaScript doesn't give an out of bounds error when using an array index that doesn't exist, it just returns undefined. The same thing happens with the destructuring assignment. So firstTraditional, firstDestructure, and secondDestructure would all be undefined now. The cool thing about destructuring is that just like with objects it lets us easily supply a default. Up here I'll just put =Steve, and now if the array element isn't found the firstDestructure will be set to Steve instead of undefined, secondDestructure will still be undefined because I haven't provided a default for that one. One more thing to mention here is even though it's empty names is currently a properly initialized array, the just doesn't happen to have any elements. This is different if names weren't initialized at all. For example if I got rid of the equals, open, close square bracket. In fact since consts have to be instantiated on declaration I'll have to change this to let to demo this issue. So now names will be undefined. JavaScript has no way of knowing that I intended it to be an array, even if I strongly typed it with TypeScript : string open/close bracket, that information is erased by the TypeScript compiler and is unavailable at runtime. Just like with what happens when we were discussing destructuring objects, if the thing you're trying to destructure is undefined or null, meaning it's not just an empty array, but strictly speaking it's not an array at all because it hasn't been set up as one, you'll get a cannot read property of undefined error when the destructuring assignment runs. Again, it's okay for the individual elements you're destructuring to be missing, they'll be returned as undefined, or whatever the default is if you specified one, but the thing you're destructuring itself has to be initialized as an array. If you're worried about that, such as if you're getting your array from a source that emits empty results, one trick you can use is to put or, empty array on the right-hand side of the destructuring statement. This is old JavaScript trick that takes advantage of the fact that null and undefined are falsey in JavaScript. When a logical or appears after a falsey value JavaScript will try to use the next thing over instead, in this case the empty array. If this value is truthy then the thing to the right of the or is ignored. This technique is useful for lots of things in JavaScript, not just for destructuring arrays. Instead of dealing with undefined arrays, let's talk about the opposite side of the spectrum. I'll set up names with many values. If you're destructuring an array that has more elements than are pulled out in your assignment statement, it's possible to add one more variable to act as a placeholder for the remainder. The way you identify that is with a triple dot. For example, I can add comma, triple dot, more here, and now a constant called more will be created as a new array that contains everything that comes after the first two elements of names. In this case, that will be an array containing the strings Charlie, Dana, and so on. The triple dot in an array destructuring is referred to as the rest operator, as in, assign the rest to this variable. Having a rest item is optional, but if you have one it has to be the last item in the list. If there aren't enough elements in the array being destructured to reach the rest item, the designated variable will get assigned an empty array, not null or undefined. The rest operator isn't just for destructuring, we can use it when declaring a function that takes many parameters. Let's say that I want to make a function that will greet any number of people that I pass in by name. I'll make a call to our hypothetical multiGreet function, multiGreet Alice, Bob, Charlie. Now, let's define a function that will implement that behavior, a function multiGreet, triple dot, items. Since we used the ES6 rest operator when we define the items argument it'll be an array. A handy function available on arrays in ES5 and higher is forEach, which lets us define a function that'll be called for each element in the array. I should note that forEach isn't available by default in ES3 environments like IE8 and lower, but it can be poly-filled, or you could just write a for loop here instead. Here I'll assume we have an ES5 environment available and use forEach, which is I feel is more semantic. Inside the forEach call I need to pass a function to call for each element. The function that I have in mind is one-liner so I'll use the super-minimalist arrow function syntax. Item, arrow, console.log, Hello plus item. Perhaps to make things a little more clear we can add some white space. And that's it. A great thing about using the rest operator in an argument list is that if insufficient parameters are specified items will be a proper array, instead of just undefined, it'll just be empty. This means that I can write something like multiGreet, open/close parens, specifying no parameters, and I won't get an error, it just won't greet anyone. A little thing about how I formatted this arrow function, while we're not obligated to include the curly braces, if you format a one-liner arrow function it's at two lines, it really doesn't hurt to include them. This may help readability and prevent an issue later if a second line of code is added to it. But it's up to you and your team. If you're team likes doing if statements without the curly braces, if there's only one line after it, then you can probably get away with it. If you prefer to use the curly braces all the time then you'll probably want to do it with your hour functions as well. You may have notice that I retyped Alice, Bob, and Charlie when calling multiGreet, even though I already have a bunch of names here in an array called names. This is because multiGreet isn't expecting an array. The rest parameter means that the function is expecting many individual parameters to be passed in separately that will then be turned into an array. Let's see what happens if I call multiGreet with names. Hmmm, no error from TypeScript, well let's see why not. Huh, TypeScript is saying that items will become an array of any, meaning this function would take individual strings or an array of strings, well really anything. Let's see what happens when I explicitly type the multiGreet function items parameter as an array of strings using a TypeScript type annotation. Now TypeScript knows that our call to multiGreet is wrong. The TypeScript type annotation on a rest parameter is for what the argument will become, not what it's expecting. By declaring items as an array of strings with a rest operator, we're saying that multiGreet expects 0 to many individual strings, not an array of strings as we passed on line 9. So how do we fix this? How do we pass an array as a series of individual parameters? Well, with ES6 we can use something called the spread operator, which oddly enough looks identical to the rest operator, … names. This tells the JavaScript engine that we don't want to pass in names as a single parameter that's an array, instead we want to pass in each individual value in names as a separate parameter to the function. The spread operator is handy in other cases too. For example, it makes concatenating arrays very simple. I'll create a new constant called names2 and set it to open square bracket, Isaac, comma, Jane. Now I can write something like const names3 equals open square bracket, … names, comma, … names2, close bracket. And now names3 will be an array of strings including Alice through Hope, followed by Isaac and Jane. I can even do something like adding Kyle here, or even here if I wanted Kyle to appear in the array between Hope and Isaac. Pretty cool stuff and I think more semantic than array.concate when there are multiple concatenations happening. So that's destructuring arrays, how to use the rest operator in destructuring and in a function parameter, and how to use the spread operator to express a JavaScript array as multiple individual elements or arguments. In the next clip we're going to talk about string templates in ES6, we provide a more powerful and flexible syntax for creating strings.
ES6 String Templates
String templates in ES6 provide a convenient and terse syntax for creating strings. Up until now we've been using the plus operator to concatenate string segments together. String templates can include variables and the result of expressions in a new string without using the plus operator. The first thing that's a bit different is that string templates don't use single or double quotes to enclose literal strings, they use the backtick character. This is the character to the left of the 1 key on US keyboards, but it may be tricky to find on other keyboard layouts. The backtick is ASCII character 96. If you don't have a dedicated backtick key, on Windows you can hold down Alt key and press 9 and then 6 on your number pad, and then let go of Alt, and it should type it. Once we've got a string literal enclosed in backticks we can begin to use what are called substitutions. Substitutions start with a dollar sign followed by an open curly brace. The body of a substitution can contain any valid JavaScript expression, such as a literal, a variable, or a call to a function. The substitution is closed with a closed curly brace. Let's refactor our multiGreet function to use an ES6 string template. I'll copy and comment out the existing code that log's greeting. Now I will change these quotes to backticks. This is valid ES6 already, meaning concatenation with the plus character works with the backtick and closed string just like it works with double or single quoted strings. But with the backtick we have the additional power of using a substitution. At the end of this first string I'll put $, open curly, closed curly. Then I'll put item inside the curly braces. After the closed curly I'll put dot. Now I can remove the rest of the expression. We don't need to use the plus character. When using this syntax it's expected that the expression in the curly braces will resolve to a value. If your editor supports TypeScript and the ES6 syntax like Atom does, you'll even get intel-sense like functionality inside the template when pressing dot after item. The TypeScript language servers knows the item is a string so it gives us the string methods in the auto-complete list. With the string template we've saved about five character versus using plus signs to concatenate the strings. However, TypeScript restores the plus signs when transpiling a string template. That means this feature doesn't provide a minification benefit to ES3 or ES5 environments, but future ES6 minfiers are likely to use it. Now we can also add single quotes or apostrophes and double quotes into the string without having to worry about escaping them, or playing games like, this string contains a single quote so I'll enclose it with double quotes, and vice versa. We can even add dollar signs and curly braces into the string without it being a problem, except if the dollar sign comes just before the open curly brace. In that case you do have to escape the dollar sign with a back slash. Since the string is enclosed in backticks, we have also have to escape any backticks that come into the string, but hopefully that's somewhat rare. Finally, when using backticks, JavaScript officially supports multi-line string without having to rely on the backslash character. Just press Enter and this is considered a new line inside the string. Note that indentation is significant when using a multi-line string, so any white space that you include in your source file will be included in the string. Something I should note is that atom-typescript has a nice refactoring available for making a regular concatenated string into a template. I'll erase our template and restore the old string. If my cursor is inside the string concatenation I'll see this lightbulb pop up in the left. I can press Alt+Enter, and atom-typescript gives me menu. I'll arrow down to the second one, which is String concatenations to a template string. If I press Enter atom-typescript rewrites it as a template string automatically.
Tagged String Templates
String templates aren't only about allowing a nicer syntax for concatenation. It's possible to do some preprocessing when on each of the elements when using a template string as well. This is a bit more of an advanced topic, but I think it might be valuable to show just to get you thinking. I'm going to type friend in front of this template to left of the opening backticks. This is called tagging a template. TypeScript gives me an interesting error, Cannot find name friend. Hmmm, if it can't find something that means it must be looking for something named friend. An ES6 tagged template must be accompanied by a function of this same name that takes two parameters, both of which will be string arrays. I'll type function, friend, strings, colons, string array, comma, … substitutions, colon, string array. The signature of a tag function must be a string array followed by a rest parameter string array. The strings parameter will be an array of all the strings that were included in the template, and substitutions will be an array of all the substitutions. ES6 will pass in each substitution to a tagged template processors function as an argument. With the rest operator we're saying, just make all those into an array. So what should the body of a tag function be? Well, technically it can anything you'd like, whatever's returned from the function is what the tag/strings template will be resolved to. However, most commonly the body of a tag function will remember something link there. So we're creating an empty array and then looping though the elements of substitutions. For each substitution we're pushing the element of string that has the same index, and then the substitution itself into our result array. When called the multiGreet, the first element in strings would always be Hello, space. And the first element in substitutions when called with names would be Alice. In our example there's only one substitution so the forEach would end at that point. Then we push the last string, which would be dot, and then return those items all concatenated together as one string used in the join function. Note that if the last part of the template is itself a substitution, such as if we didn't have a period here, and the curly brace was the last character of the tag template, ES6 and TypeScript automatically add a blank string element into the strings argument. This lets us safely skip an undefined check here. Strings will always be exactly one element longer than substitution. This is boilerplate code so let's actually extract it as its own method card called processTaggedTemplate. And then we can call it in our friend/tagged function. ProcessTaggedTemplate strings… substitutions. Okay, now I'm noticing something a bit odd here. Friend is expecting a series of parameters to be passed in that we wrested together in the substitutions argument, that's fine. However, we're then taking that argument and spreading it out again because processTaggedTemplate is also expecting a series of individual parameters. There's no sense doing double work here so let's modify our boilerplate function and make it just take an array by getting rid of the rest operator. Then we can eliminate the spread operator and friend, which means that it'll just pass substitutions as an array, that's better. Oh whoops, I forgot to put return here, let me fix that. Now what do we do inside friend? Well, I'll tell you a story. Fairly recently I received an advertisement email that started with Dear null, well rather than let one of our customers go through an experience like that let's check for a falsey value here and replace it with friend. If not substitutions 0, substitutions 0 = friend. Great. Now if a null, undefined, or blank string is provided as the first substitution to any string template tagged with friend, it'll be replaced with friend. There's plenty of other sorts of tags we could write. I've seen people suggest automated HTML escaping, or URL escaping, or even the creation of well-formatted HTTP headers. Also possible might be conditionally logging any of the passed in substitutions to the console of a debug variable is set. Or how about throwing an exception in the presence of a particular value in the first substitution. It really could be anything, it's quite open. Don't forget though, you don't have to use a tag if you just want the template substitution functionality. If you don't specify a tag on a shrink template it works just like a more terse version of concatenation.
Using the ES6 for of Loop
There's one more ES6 feature that I want to cover in this module, and that's a new looping construct called for of. Imagine we have an array of names like we were using for destructuring. If I want to log out each of the names in this array I have a few ways of doing that. We already saw the forEach array function, which is available in ES5 environments. Also in ES5 environments I could use something called a for in loop. For in loops iterate over an objects properties. When used on an array a for in loop enumerates through the array's indexes. In our case it would loop from 0 to 7. If I wanted to log each name I could write console.log names, open square bracket, item, close square bracket. And then each name would be accessed by index when the loop ran. The new looping syntax available in ES6 is a for of loop. For, let item of names. Instead of enumerating the array indexes line for in does, for of enumerates the values. I could just put console.log item here, and the JavaScript engine would log Alice, Bob, Charlie, etc., when this loop runs. For of works on anything that is enumerable, even strings. Let's have a constant called instrument set to the value of guitar. If I run this through a for of loop the JavaScript engine will log capital G in the first time through, then u, and then i, and so on. In ES6 for of is an improvement to looping through a string by character index because for of accounts for Unicode characters beyond the basic multi-lingual plane that are represented by surrogate pairs. Looping by string index does not consider surrogate pairs as a single code point, which means that it's easy to mishandle such characters when using that method. The reason that I bring this up is that the transpile code generated by TypeScript when using for of and targeting ES5 unfortunately does not account for surrogate pairs when looping through a string. This means that if I were to replace the word Guitar with Unicode code point 127928, otherwise known as the guitar symbol, this for of loop would execute twice when compiled using TypeScript 1.6, why is that? Well let's look at the transpile code. I'll press Ctrl+Shift+M to bring up the emitted JS in the right-hand panel. Sure enough we can see that the transpilation of the for of loop is a regular loop that counts by index. As we said, enumerating a string by index will not account for surrogate pairs that represent a single Unicode code point. So this loop will execute twice and log two invalid characters instead of executing once and logging the guitar code point. So this is something to be aware of, if you're using TypeScript and targeting ES5, a for of loop will not work correctly if enumerating strings containing characters outside the basic multi-lingual plane. As an FYI, Babel does transpile the behavior of a for of loop on a string with surrogate pairs correctly, however, the Babel emitted code requires a dependency on the CoreJS poly-fill library, meaning it's out of the scope for this course. The TypeScript team does have this listed as issue 2585 on the TypeScript GitHub page and it's currently slated for TypeScript 2.0.
Module 2 Summary
In this module we covered much of the new syntax that's currently supported by TypeScript. We saw how const and let can clarify the scope of variables, and we discussed when using var may still be appropriate. We looked at arrow functions and talked about how they offer a terse function syntax that also adds predictability to what this references. We covered destructuring of objects and arrays, and discussed the new ES6 rest and spread operators. Then we took a look at ES6 string templates, including tag templates. Finally we took a quick look at the new for of loops syntax in ES6. Because TypeScript acts as a transpiler you can start using these new ES6 syntax features in today's ES5 environments. In the next module of this course we'll move on from general syntax features and spend some time focusing on ES6 modules. That's coming up next in Using ES6 with TypeScript.
ES6 Modules
ES6 Module Syntax and the Missing Module Loader
Welcome back to Using ES6 with TypeScript. In this course module we'll talk about the new module syntax in ES6. You heard that right, this is a module about modules. In recent years modules have been an interesting, and sometime controversial subject among JavaScript developers. Technical Committee 39, which is the group that develops the ECMAScript standard added official syntax for defining modules to ES6. We'll be covering that syntax in this course module. What is not yet been officially standardized is a runtime module loader spec. In fact, the ES6 specification explicitly leaves this to be defined by the implementation. The two most common environments that implement JavaScript are the browser and NodeJS. NodeJS applications typically load JavaScript files from the local file system, and of course, browsers fetch JavaScript files over comparatively high latency HTTP network connections. It's not surprising that the differing parameters of these two environments make it difficult to create a module-loading standard that is ideal for everyone. The task for developing that standard currently falls on the WHATWG, or the Web Hypertext Application Technology Working Group. As of February 2016 that spec is still very much a work in progress. If you're interested you can read more about it at this URL. You could be forgiven for thinking that it's pointless to have added a standard module definition syntax to ES6 without also adding a standard way to load those modules. The good news is that the ES6 module syntax was not designed in a vacuum. With a transpiler, like TypeScript it's possible to write your code using the new ES6 module style and have that code transpiled for compatibility with today's external module loaders. TypeScript 1.5 and higher supports transpiling ES6 module syntax to four module formats, CommonJS, AMD, UMD, and System. In this course module we'll see how to use the ES6 module syntax with each of these four formats, along with a popular loader that implements the format.
Introduction to ES6 Modules
ES6 modules provide a file-oriented mechanism to organize your code base. Typically, objects created at the root of a JavaScript file are added to a shared global environment. This means that objects in one file may implicitly reference objects declared in a different file in the global scope. Modules change this behavior. When a JavaScript file is first converted to a module objects declared in the module are accessible in that module only. Code in other files can't get to them. To permit the use of objects declared inside an ES6 module, the objects must be explicitly exported from the module where they're declared and then imported to the module where they'll be used. Let's take a look at some example code that lives in the global scope, and then we'll modify it to use ES6 modules. I've created a new JavaScript library called wowify.js. Its main function takes a set of strings and returns them as an array with WOW added onto the end of each. I'm sure that this very useful library will catch on. In order to use this library I created a TypeScript file called program.ts. It creates an array of interesting things and then passes them into the wowify function as a set of individual elements using the spread operator. TypeScript knows that the wowify function lives in the global scope from the other files, so it assumes that it'll be available in the global scope here. You can see that I get a tool tip for it and it knows about the signature, the return type, and even sees the JS dock. The results from wowify are added to a div called result separated by line breaks. I've got both of these script referenced in a file called ModuleDemo.HTML. Here you can see both wowify.js and program.js referenced at the bottom of the file. I have to make sure that wowify is listed in the HTLM first so that wowify will be available when program.js runs. If I listed these out of order program.js would fail with a wowify is undefined error. Let's launch our web server and take a look. I'll open my Command Prompt and run node server.js. If you're following along be sure to go through the readme in the sample code folder so that this'll run correctly. I'll open localhost 3000 and click Module Demo. Sure enough we see that the items in our array have been wowified. Looking back at our HTML file I said that it was necessary to list wowify and program.js in the correct order. That's fairly easy to do in a trivial application with just two files, but these days JavaScript applications often have dozens, or even hundreds of JavaScript files. It's very difficult for a human to manage a dependency matrix of that size manually. A good solution is to provide a way for each JavaScript file to identify its own dependencies. This allows the computer to calculate and manage the module dependency matrix. Just such a solution is enabled by using modules and the module loaders will cover. In the next clip we'll convert wowify into a module.
Converting a File to an ES6 Module
In this clip we're going to talk about how to convert a JavaScript file to an ES6 module. With TypeScript a file is considered a module as soon as something is either exported from it or imported to it. Since wowify doesn't have any external dependencies we won't need to import anything. However, we would like for the wowify functionality to be useable in other code files. The way we do this is with the export keyword. Exports can be performed on individual members in the module, or in a single list that contains all of the exports for the module. For example, I can write export in front function wowify and now the wowify function will be available to import to other code files. I could also remove export here and instead use a separate export declaration, export, open curly, wowify, close curly, semi-colon. If there were additional objects to export I could put a comma and then type the name of the other objects. SomeFunction, someVariable, someClass. Of course TypeScript gives me errors because these objects don't exist. How about I create a new function called mehify and export it along with wowify. And we'll add it to the export. One nice thing about using this separate export declaration is that it permits alias in the export objects. I can write as superWowify here and now instead of wowify being exported as wowify it'll be exported as super wowify. Renaming an exported object can be handy for simplifying the API a module exposes, or for allowing functions or objects inside a module to be renamed without necessarily changing the module's API. The ES6 syntax to rename an object upon export is different than the ES6 syntax used to rename a variable in an destructuring assignment. As we can see with ES6 modules exported objects are renamed with the as keyword. You may remember that destructured variables are renamed using a colon. This discrepancy is a bit unfortunate, but it's something you'll just have to memorize. I'll remove this alias now so that wowify will be exported as wowify. So that's exporting. In the next clip we'll talk about importing a module using the ES6 syntax.
Importing an ES6 Module
In this clip we're going to talk about how to import ES6 modules. Now that wowify is a module we'll have to import it anywhere we want to use it. There's two basic ways that we can choose to do the import. The first is to import everything in the wowify module to a placeholder object and then dot off of the placeholder to get what we need. The second is to import only the objects we need from the module into the local variables. For example, if we don't need mehify we don't need to have it in scope. In program.ts I'm now getting the error Cannot find name wowify. As I mentioned before, as soon as you either import something or export something from a TypeScript file that file becomes a module. This means that its objects are no longer available on the global scope so we'll have to import it. The ES6 syntax to import everything that's been exported from a module into a placeholder variable is import star as placeholder name, wowify in this case, from path to module excluding .js. In this case the path will be ./wowify. So now we've imported each thing that was exported form the module that lives in the current folder called Wowify and stored in all in an object called wowify. Funny, our error didn't go away, but now it's something different, Cannot invoke an expression whose type lacks a call signature, do you know why? Well, this syntax says that we want to import everything that was exported into a placeholder called wowify. Each exported object will be a property on that placeholder. It's technically called a module namespace object. This means I can type wowify. and I'll see the completion for both wowify and mehify. So the fix is to change the call to wowify.wowify and then the error goes away. I don't want to have to dot off of a placeholder for wowify though, so let's revert that chain and try something else. If I want wowify to reference the exported function and not the module I can use some syntax that looks a little like destructuring, import, open curly, wowify, close curly, from ./wowify. This syntax instructs the environment that there will be one or more things exported from the module in the same folder called Wowify and we want the exported wowify object to be assigned to a local variable also called wowify. Cool stuff. Now our TypeScript knows that wowify is a function again, though we're accessing it as an exported member of a module. The mehify function isn't in scope anymore, but if we still wanted to get it we could put comma mehify here and it would be imported too. Again, you may think that this looks and sort of feels like a destructuring assignment, but it's not. ES6 module import statements also use the as keyword to rename imported objects instead of the colon, which is what's used to rename things when you're doing destructuring. So I could rename wowify to w, and then access it here using w, and everything would work.
Default Exports
There's one more feature of ES6 module syntax that I want to show you and that's default exports. If you create a module and, if most of the time people are going to want to use one object from it, it's possible to mark that one object as the default. For example, in the wowify module most people are going to want to use the wowify function. I can mark wowify as a default export by typing as default. However, now program.ts is broken with the error, wowify has no exported member wowify. This is true. You may have noticed that the syntax to mark an exported object as a default was suspiciously similar to this syntax to rename an exported object to default. The funny thing about ES6 default exports is that they are literally just an export named default. And I can say literally because we actually wrote it down. This means that I can write default as here and now the default export from wowify, which has been renamed as default, will be imported and renamed back to wowify. Now I think the designers realize that this syntax is a bit awkward, so I'm happy to say that there's a shorthand to import default exports. And that's to just specify the local variable name, import wowify from ./wowify. This is the same as saying default as wowify. While we're at it it's also possible to combine the two. For example, I can put comma, open curly, mehify, close curly here, and mehify would be imported as mehify, and the default export would still be imported as wowify. Depending on the kind of library you're writing, it might be useful to have a default export. The trouble is that it can be awkward to force consumers to use different import syntax for the default object in the library versus any other objects. There's a happy compromise though, and that's with doing a double export for the default object. In wowify.ts I'll add wowify as an export again. Now a module that imports from wowify can access either wowify or mehify using the curly brace syntax, or the developer can choose to access wowify only by the shorthand default export syntax.
ES6 Module Syntax Review
Before we cover how to use the popular module loaders, let's quickly review what we've learned. A module is anything that either imports something or exports something. It's possible to export a variable, a class, or a function from a module. With TypeScript it's also possible to export other things like namespaces and enums, but since those aren't ES6 standards, they're out of scope for this course. To export an object simply put the keyword export in front of this declaration. So, function doStuff becomes export function doStuff. I should note that there's also a shorthand to export an individual member as a default if you want, and that would be export default function doStuff. Remember that exporting something as the default simply means that it is exported with the name default. In addition to exporting an object directly it's also possible to use the keyword export with a pair of curly braces and specify a comma-separated list to the objects in the current file that you'd like to export. To rename an exported object just use the as operator and specify a valid JavaScript name. There's three main ways to import a module using the ES6 syntax. The first is to import all exported objects into a placeholder object. Import star as placeholder from path. This lets you dot into the objects off of the placeholder object. The second is to use curly braces and a comma-separated list of things to import. This syntax allows direct access to the imported objects as local variables. This feels a bit like a destructuring assignment, but the syntax for aliasing if different. To rename any of the imported objects it's necessary to use the as keyword. To access a default export with this syntax you can specify default as the item to import and then provide an alias to assign a name. Finally, the last method to import a module works only with the default export, import myObject from path. Notice how there are no curly braces. This code will import the default export from the path aliased as myObject. And that's it for exporting and importing with the ES6 module syntax. Coming up we'll see how to actually get these modules working with popular module loaders. First, let's try out the browser with AMD modules and RequireJS.
Using AMD and RequireJS
The first module loader we'll look at is RequireJS. The goal of this clip is simply to show you how to get RequireJS working at a very basic level with TypeScript and ES6 modules. RequireJS is designed for use with the AMD module format, which stands for Asynchronous Module Definition. AMD modules are most commonly used in a browser. You can get RequireJS from requirejs.org, or from package managers like npm or NuGet. I've already downloaded RequireJS from npm and made it available on my web server. Be sure to follow the instructions in the readme file in the sample code for how to do that if you're following along. The first thing we need to do is to get TypeScript to emit our modules in the AMD format. That settings in our projects to use tsconfig.json file in the compilerOptions object. I'll set the value of module to amd, in all lower-case. Then I'll open up wowify.ts and save it and program.ts and save it. TypeScript should have emitted both files in the AMD format now. To confirm, I'll press Ctrl+Shift+M in atom-typescript to preview the emitted js of program.ts. The hint that they emit in AMD is that it's wrapped in a call to a function named define. The define call emitted by TypeScript always passes in an array of strings and a function. The array of strings identifies the dependencies of the module. TypeScript's AMD modules always register a dependency on require and exports, which are part of the AMD format's plumbing. Since program.ts imports wowify we also see that listed as a dependency using the same path we specified in the ES6-style import statement. This signals to RequireJS that it should allow wowify.js in the same folder. The JS extension is implied. The second argument path to define is a callback that'll run once each of the dependencies have been asynchronously loaded. The dependencies are passed in as arguments in their respective position, first require, then exports, and then wowify. TypeScript will sometimes preemptively alias things to avoid naming conflicts in the emitted code, and that's what this _1 tag thing is, don't worry about it. The code we wrote inside program.ts has been transpiled and will execute inside the body of the callback. Let's open this in the browser and see how it goes. I'll launch the web server by running node server.js from the Command Prompt. Hmmm, now it looks like nothing happened on the page. If we press F12 to open the DevTools though and look at the Console tab we'll see one of my favorite JavaScript errors, define is not defined. That's because we've converted our scripts to AMD modules, but we haven't setup RequireJS yet, let's do that now. In ModuleDemo.HTML I'll remove the two script tags that load wowify and program. Then I'll add a new script tag to load RequireJS. I've already downloaded RequireJS via npm. You could also get it via Bower, NuGet, or even directly off of requirejs.org. After loading RequireJS inspects its script element from an HTML file data tag called data-main. If data-main is present require will load and run the reference script. I'll type data-main= program. This instructs RequireJS to load a script called program.js in the same folder as the HTML file. When I refresh the browser we can see that everything is working again, great. Let's look at the Network tab in the DevTools. We can see that require.js was loaded here. After that on our timeline program.js was loaded, even though it wasn't referenced directly by a script tag. The initiator column informs us that it was loaded by require.js. Require.js loaded program because it was listed in the data-main tag. It then saw that program had a registered dependency on wowify so it loaded that too. Before we were using modules wowify had to be loaded first in order for program.js to work correctly. Now the load order is handled automatically by require.js. Even though wowify was loaded after program, program still worked because require delayed its execution until all of its dependencies were available, pretty cool.
Ambient External Module Declarations
One tricky thing about using RequireJS with TypeScript is getting the module names that TypeScript uses to line up with the URLs needed by RequireJS at runtime to load the JavaScript files. A good way to demonstrate this problem is to change our program to use JQuery instead of using native DOM manipulation. I'll comment this line and change it use a JQuery selector and the HTML function. This JQuery code has exactly the same functionality as the line we just commented. TypeScript already knows about the dollar signs since I previously downloaded the definition file for JQuery from DefinitelyTyped. For more information about how to download definition files you can watch my other Pluralsight course, Practical TypeScript Migration, or look in the readme in the sample code for this course. Let's take a look at the bottom of the JQuery definition file. The TypeScript thinks that the $ should be available globally because the definition file for JQuery declares it as a global variable. This is based on the assumption that most web pages using JQuery load it directly in a script tag in the HTML. So the $ will be available in the global scope. Instead of doing that we'd like to have JQuery be loaded by RequireJS as a module. The wonderful folks who created the JQuery type definition provide for that by this block of code, which is called an ambient external module definition. In TypeScript it's possible to declare a module using a string instead of a normal JavaScript compatible identifier. This registers the string with TypeScript as a special identifier to be used with module import statements. With the way that this type definition file is written if a module in this project imports the string jquery TypeScript will know that the type of the imported object is a JQueryStatic. For purposes of demonstration I'd like to modify this definition to eliminate the global $ and JQuery variables. That'll help us be sure we're doing the module import correctly in program.ts. Just to be clear, you won't have to make this change to the JQuery type definition file in order to use it with an import statements. I'm only doing this to help illustrate how TypeScript thinks about these ambient external module declarations, the ones that are declared with strings. So I'll comment these two lines. Now we get the error Cannot find name $, which is expected because we just removed the $ declaration. Inside the JQuery ambient external module declaration I'll write var $: JQueryStatic, and now the export works again. We don't have to use the declare keyword with var since we're already inside a blocker code that is marked with declare. With this change $ in JQuery will no longer be available on the global scope according to TypeScript. But TypeScript will still allow us to import the string jquery as a module. When we do the imported object will be of type JQueryStatic because that's the type of the variable we're exporting. Back in program.ts we now have an error, Cannot find name $. This is because we just commented the code that told TypeScript that jquery would available globally in the $ variable. We can fix this by importing the string jquery. Import star as $ from jquery. The sting jquery here in this TypeScript file is not a path, it's an identifier. It corresponds exactly to the ambient external module declaration used in the JQuery definition file that we just edited. If we hover over the $ later in the file we can see that TypeScript knows that $ is a JQuery object. This isn't a global $, it's a $ that we just imported. Let's take another look at the emitted JavaScript for program.ts. Now the define call includes jquery in the list of dependencies, that's better, but here's the tricky part. RequireJS won't know where jquery is. As written, requirejs will actually just look for a file called jquery.js in the same folder as program.js. Without telling it otherwise require assumes that the dependency strings other than require and exports represent JavaScript files. On our web server jquery isn't' in the same folder as program.js, it's under our scripts directory. This means that the dependency on jquery will fail to load at runtime with an HTTP 404 error. There's one more piece to the puzzle and that's using the RequireJS Configure feature. This feature allows you to provide mapping between module identifiers and URLs among other things. The way to configure requirejs that I found works best with TypeScript and AMD modules is to add an extra script above the requirejs script tag that defines the configuration in an object literal called require. When requirejs loads it checks for an existing global variable called require and if present it'll use that variable to configure itself. In the Public folder of our project I'll create a new file called require-config.ts. The Public folder maps the route of the website. In here I'll define a variable called require with a type RequireConfig. The RequireConfig interface is declared in the require type definition that I downloaded from DefinitelyTyped, and is included in the sample code. If you haven't downloaded that type definition file you'll probably want to explicitly type the require variable as any to avoid potential type mismatch issues if require is defined in different files. On that note I'm using var here as opposed to const since I intended for require to be a global variable that is safe to re-declare in different files. I can set require to an object literal with paths property. In the paths property I'll add a property called jquery and set it to scripts, jquery, jquery.min. RequireJS will always append .js on the end so you must omit it. Now I've got a reference require-config in the HTML file. Just above the requirejs script tag I'll add a new script tag that references ../require-config.js, and that's it. When we refresh the page in the browser it works as expected. In the DevTools network tab we can see that require-config loads followed by require.js. Then require.js loads program, wowify, and jquery, cool stuff. There's plenty more that RequireJS can do than we covered in this module. If you'd like to dive more deeply into RequireJS please check out Jeff Valore's RequireJS course on Pluralsight after completing this one. Next we'll attempt to get wowify working using the CommonJS module format in NodeJS.
Using CommonJS and Node
In this clip we'll get wowify working with NodeJS. This should be a lot faster since our code is already set up to use external modules. First, I'll open the tsconfig.json file and set the module format to use commonjs, all lower-case, and save. Then I'll copy program.ts to a new file, programNode.ts. I'll open programNode.ts and save it and wowify.ts and save it. If I press Ctrl+Shift+M I'll see the emitted code for wowify. You can identify a commonjs module that exports members because it will add them as properties to a seemingly global exports object. Another trait of commonjs modules can be seen if I view the emitted source for programNode.ts. Here the import statements have been converted to calls for the global require function. Since we're intending to run this code on the console in NodeJS it doesn't make sense to use jquery anymore, so I'll remove that import. To make the output look interesting though I'll use a library called chalk, which I previously fetched from npm along with its TypeScript definition from DefinitelyTyped. Import star as chalk from chalk. Now I can comment the jquery call and instead write console.log, chalk.bold.yellow, open/close. Then I'll copy the result from before and change the join string to be the new line character. On the command line I'll quit the web server with Ctrl+C, that'll run node Modules, programNode.js. Our interesting things are printed in bright yellow, talk about wow. If that seemed a lot easier to set up it's because it was. First of all, NodeJS has a CommonJS module loader built into it, so we didn't' have to set up something like RequireJS ourselves. Secondly, modules obtained from npm follow specific conventions and come with metadata files that help lessen the requires configuration. And thirdly, NodeJS can cheat a bit while resolving modules because it has access to the file system. It's not prohibitively expensive for Node to try a few well-known places on disk and read through configuration files when trying to find a module. On the other hand it would be terrible for performance if RequireJS asked the browser to open multiple HTTP connections per script so that it could search around. The import statement for chalk doesn't include a relative path, meaning it doesn't start with dot or dot, dot. With this style of module identifier NodeJS looks in its node_modules folder for a subfolder of the same name. In this case it can see a file called Index.js in the chalk folder so it loads that. Additional configuration options are available in the package.json file. So there was a lot done for us when using Node due to the conventions of the CommonJS module format versus what was needed with AMD and RequireJS in the browser. There are also tools that allow the use of CommonJS modules in the browser. Two common ones are Browserify and webpack. If you're interested Joe Eames and Jeff Valore have published Pluralsight courses on those packages.
Using the UMD Module Format
There's another module format supported by TypeScript that was added in TypeScript 1.5 called UMD, that stands for Universal Module Definition. This is an interesting module format since UMD-formatted modules can be loaded in either NodeJS or in the browser via RequireJS. I'll open the tsconfig.json file and set the module format to umd, all lower-case. Then I'll open and say wowify.ts, program.ts, and programNode.ts, that's it. If I press Ctrl+Shift+M on wowify.ts I'll be able to see the emitted js. The clever part about universal modules is in this top part. At runtime scripts in the UMD format check for global variables that are distinctive between AMD and CommonJS, and depending upon which globals are found the module will be initialized in the appropriate manner. If you're writing a module that is appropriate to use in both CommonJS and AMD environments you may wish to consider using the UMD format.
Using SystemJS
In this clip we're going to demonstrate how to use the SystemJS module loader with TypeScript. The SystemJS is rather strict JavaScript module loader that enables use of the proposed System.register format for modules. This is achieved by using a poly-fill that implements a system object compatible with the official ES6 module loader specification as it existed in late 2014. That module loader specification didn't make it to the official ES6 release. In addition to ES6 module support, SystemJS also supports loading AMD or CommonJS style modules, Shim in global JavaScript code files and even loading non-JavaScript assets like CSS, JSON, or image files. Support for transpiling modules to the System.register format was added in TypeScript 1.5. This is the format that was intended for use with SystemJS, or the future official JavaScript module loader implementation. There's two ways to use SystemJS with TypeScript. One is to use a TypeScript loader plugin, and the other is to transpile the TypeScript to JavaScript as part of a centralized build process and just load the final JavaScript at runtime. I like the workflow of compiling code at develop time and serving regular JavaScript to clients, so that's what I'll demonstrate in this clip. If you want to investigate the other method, which involves serving the TypeScript as-is and compiling it on the client side, check out plugin TypeScript at this URL. I can instruct TypeScript to emit code using the System.register format by opening our tsconfig.json file and changing the module property to system. Now open program.ts and save it, and wowify.ts and save it. Let's take a look at the emitted JavaScript. The ES6 module loader spec called for a new JavaScript global variable called system, which would have a method called register. In this setup we're calling register with an array of string that represents the scripts to load. Those are our dependencies. In callback this code returns an object literal that includes setter functions for each of the dependencies, and an execute function that contains the actual code we wrote. These three lines in program.js correspond to these three lines in program.ts, the rest is boilerplate, which TypeScript thankfully handles for us. Now that we've had TypeScript transpile our code into the System.register format we need to set up the SystemJS loader. In the HTML file I'll delete the code that references require js. Then I'll add a new script tag that references systemjs, which I've already downloaded from npm. You could obtain systemjs by running npm install, systemjs, which is the best method. Or it's possible to download it form GitHub at this URL. Similar to RequireJS, we'll have to provide a configuration script to SystemJS to instruct it where jquery lives, otherwise the browser would request a file called JQuery in the current folder. I'll add another script tag for ../system-config.js to hold our configuration. Let's create that file now. I'll create a file called system-config.ts in the Public folder, which is the root of our website. SystemJS is configured via a call to System-config. I already have the SystemJS TypeScript definition file installed so TypeScript has some idea of what System looks like. In the config call I'll provide a object literal with a map property. The map property needs a property called JQuery, which will map to the path of the minified jquery script in our server. Note that with SystemJS I have to add the .js in a mapping, whereas with RequireJS I was forced to leave the .js off. There's one other thing that needs to added to your systemjs config when using TypeScript and that's a packages object with a blank string property that contains a default extension property set to js. The blank string here is a catch all. It basically means to apply this option to any import that isn't set up with an explicit mapping like jquery is here. What this does is tell SystemJS to automatically add a .js extension to import strings at runtime. So, for example, in program.ts we're reloading wowify. The default extension switch tells SystemJS to assume that wowify means wowify.js. Without default extension set up I have to change this import statement to ./wowify.js., which in turn would mess up TypeScript's module resolver. With the default extension properties set TypeScript can do its thing at compile time and SystemJS can do its thing at runtime. The last thing we need to do to attempt this is reference our program file. With SystemJS the most efficient way to do that is with an inline call to System.import. I'll open a script tag and write System.import, program. You may recall that with RequireJS we achieve the same functionality by using a data-main property on our script tag. Now refresh the browser, huh, no text. I'll hit F12 and open the consult tab. Huh, $ is not a function. Well, it turns out that this a very interesting error that is instructive about how ES6 modules are intended to work. We'll discuss it further in the next clip.
Using SystemJS with ES5 Exports
Let's further examine the detail of how the ES6 import star syntax works and how SystemJS chooses to model it when loading ES3 or ES5 libraries like JQuery. Earlier in this module I explained how import star means import each thing that is exported from the module and assign it to a placeholder object, officially a module namespace object. So in this illustration here we have a module that exports properties foo, bar, and baz, and also a property marked as default. If I were to import this module using the syntax, import star as thing from the module path I'd be able to call thing.foo, thing.bar, thing.baz, and thing.default. Let's refer to this as the has a structure. This ES6 module has foo, has bar, has baz, and has default. With ES6 modules module namespace objects are always inert. It's part of the design that anything declared in this middle part should not be accessible externally. In our diagram the circle here represents the code in our module that isn't explicitly exported. However, libraries written prior to ES6 don't expect to follow that rule. Let's use JQuery as a specific example. The JQuery library is structured as a function that has many properties on it. Here I've represented the JQuery function itself as the middle circle. This function is what's commonly aliased as $. I've also illustrated a small sample of the JQuery function's properties, map, ajax, and extend. Because of these properties JQuery follows the has a structure, meaning it has map, it has ajax, it has extend, but it's different than the ES6 module we were discussing earlier because it is also an is a, JQuery is itself a function. So what happens when we make the call import star as $ from jQuery? As before, this means that we want to take everything hanging off of the module on that path, ./jQuery in this case, and assign it as a property of the $ object. Okay, great, so this means I can call $.map, $.ajax, and $.extend, all the has a's work fine, however, the canonical use of JQuery is as a selector, calling the $ function itself directly, $ myDiv. Notice it's not $.something, we're calling $ itself as a function. But ES6 module namespace objects are intended to be inert and SystemJS treats them as such. This explains where we got that $ is not a function error in the last clip. SystemJS was being strict about how ES6 module namespaces objects work in a way that we didn't experience with either RequireJS or the CommonJS implementation built in the NodeJS. SystemJS dutifully imported the properties on the JQuery function, the has a's, but it didn't allow $ to be a function, the is a, so how do we fix this? Well, the way that SystemJS supports is a exports when loading an ES5 or ES3 library is via a synthetic default export. When loading non-ES6 modules the SystemJS module loader automatically promotes the module to work with the default import syntax. Even though there isn't an export called default on JQuery the $ function will still work as expected when using import$ from JQuery along with SystemJS. Let's try that now. Okay, so here we are back in program.ts. I'll change the import line to import $ from jquery. If we open the emitted JS we can see that TypeScript is calling into a default member of the JQuery object. Remember that when loading CommonJS, AMD, or global modules, SystemJS makes the full module available at the default member, so this works correctly to load jquery as long as we're using SystemJS. Let's check the page in the browser, sure enough it works okay. I should mention that TypeScript 1.8 makes this much easier due to a new feature that embraces the default load an functionality of SystemJS. Since the module style is set to system in tsconfig.json the default import syntax works automatically. In TypeScript 1.7 and earlier though you'd get the error Module jquery has no default export, which is technically correct, jquery doesn't have an export name default. If you're using TypeScript 1.7 and you get the Module has no default export error, you're best bet is manually add a new default export to your TypeScript definition files to make TypeScript happy until you have the chance to upgrade. TypeScript 1.8 does make integrating with the SystemJS loader much easier. So that's the Using ES6 module syntax with TypeScript and the basics of how you use RequireJS with AMD, CommonJS with Node, and SystemJS with the System.register format and non-ES6 libraries. We also briefly discussed the UMD module format, which will work any of those loaders at the cost of slightly larger script sizes. In the next module we'll cover another major syntax feature of ES6, classes.
ES6 Classes
A Multi-paradigm Object Story
Welcome back to Using ES6 with TypeScript. In this module we'll talk about ES6 classes. Before ES6 JavaScript developers had many excellent tools in their toolbox to enable programming with objects. Among the best of these tools are prototypal inheritance or delegation, the simple object literal syntax, and patterns involving factory functions. ES6 adds a class-based single-inheritance manner of object oriented programming to JavaScript. ES6 classes might feel somewhat familiar to developer coming from other languages, such as C#, or Java, though there's much less ceremony. TypeScript supports either transpiling ES6 classes to a constructor pattern that works in ES3 or ES5 environments, or it can emit classes natively when targeting ES6. With ES6 JavaScript objects story is now multi-paradigm. Developers who prefer object composition should still use prototypal inheritance with objects linking to other objects. Developers who instead prefer the familiarity of class hierarchies now have first class syntax support for that in JavaScript, and they could choose to write code using that pattern. In this module I'll provide an overview of ES6 classes and I'll highlight some of the small differences between what's legal in ES6 and what TypeScript will compile without error.
Creating and Using an ES6 Class
In this clip we'll see how to create and use ES6 classes. Let's imagine that we're writing a simple program to keep track of contacts. A contact may have a name, a phone number, and email address. The way we could write this using ES6 class would be this, class Contact, open curly brace, close curly brace. This is a capital C because I intend for people to call it with a new keyword. That's a JavaScript convention though it is legal to have ES6 classes that start with a lower-case letter. Now I'll call and use it. Const alice = new Contact. Alice.name = Alice, alice.phone = 555-1212, and alice.email - alice@example.com. Then I'll do console.log, JSON.stringify, alice. This is valid ES6 code, so ignore those TypeScript errors for a moment. An important thing that I want you see and really immediately understand is that code that uses ES6 classes is still JavaScript. Here on line 4 we're calling our class with the new keyword. That's the same thing we do if we were using the JavaScript constructor pattern. The call creates a new JavaScript object. Later we're assigning values to properties on that object and then logging the results of the console as a JSON string. When I run the script with NodeJS we get the JSON representation of the object, which includes the name, the phone, and the email address. Let's look at the JavaScript emit, which I open in Atom by pressing Ctrl+Shift+M. By the way, off camera I switched my tsconfig back to use CommonJS. The class is emitted as IIFE that returns a constructor function. Alice gets instantiated as an object and then we make assignments to properties on that object. In JavaScript assignments to properties on an object will create those properties if they don't yet exist. So this code works just like it would with any other JavaScript object. In fact, the output of this trivial program would be the same if we wrote it using an object literal, const alice = and the object. I'll run it and the output's the same. So we can see that in many ways instantiated ES6 classes behave like normal JavaScript objects. I'll put back in our new call. Let's see what happens if I don't set the phone number on alice. The phone number disappears entirely from the output when run form the Command Line. If we intended for the property to be there that might be an issue. When using TypeScript, one way to ensure that a property and a class instance always has a value is to assign one inside the body of the class. Name = blank, phone = blank, email = blank. This pattern would be familiar to Java, C#, and Visual Basic developers, but technically it isn't valid ES6. When we save, TypeScript transpiles these ambient assignments to live at the top of the class constructor in the same order in which they appear in the body of the class. We'll talk more about constructors in a later clip, but just be aware that if you're coding native ES6, assignments like this belong in the class constructor, not in the body. Now when I call new Contact each of these properties on the object will be set to blank string. When I run the program phone is once again present and set it to its default. You might notice that the TypeScript errors have also gone away, this is due to type inference. We've now established that these three properties exist on the Contact class and they're of type string because the type of value that we first set them to. In the JavaScript emit we can see that these property assignments now make it into the Contact function. When new is called on contact this will be set to refer to the new object that's implicitly returned. So the property's name, phone, and email are all added to that new object with the value of blank string. And then the new object is referenced by the alice constant. If we wanted phone to start as null, TypeScript would implicitly type phone as any. In this case we could add a type annotation like : string, and that would satisfy the type checker. In fact, with TypeScript is we didn't care about defaults we could simply leave each of these properties listed along with the type annotations and get rid of the assignments, name: string, phone: string, email: string, this puts us back in the place where we started except that now TypeScript is happy with lines 8 though 10 since it knows that these properties are all strings. Notice in the JavaScript emit the properties are all gone again. Also, if we comment phone and run the program again, phone is missing. The indication that there may be a property phone here is erased since type annotations are a TypeScript only concept, only assignments are transpiled. So that's declaring and creating instances of a class with primitive properties.
Using a Constructor
In this clip we're going to discuss constructor functions. ES6 classes automatically have an implicit constructor, that's why I put the parentheses here, they reinforce that we're calling a class constructor. With ES6 it's possible to add an explicit constructor to a class, and that's via a special function called constructor. Constructor, open/close parentheses, open/close curly braces. Adding an empty constructor like this has no effect on the class, but we can add parameters like this, name, phone, email. Now notice that TypeScript is saying that we're not creating the Contact class correctly, Supplied parameters do not match any signature of the call target. Let's pass in values to the constructor now, Alice, I'll add a phone number again, and the email address. Now these parameters are being passed into the constructor function, but they're not actually getting used yet. Inside the constructor body we need to assign each parameter to the corresponding class property. The way it acts as a class property is via the this keyword, this.name = name. This.name is the class property and name is the parameter. This.phone = phone, this.email = email, very good. So what if I wanted to allow email to omitted? As with other syntax in TypeScript I can put a question mark after email and now it's optional. The question mark is TypeScript-specific syntax. In JavaScript function arguments are always optional. While this is very flexible it can also be a source of bugs. TypeScript was designed to help prevent programming mistakes, so it flips this role around and makes passing a value for all arguments mandatory unless the argument is specifically marked as optional. Another way to mark a parameter as optional is to specify a default. I can type = undefined here and now email will be optional again. Using undefined as a default is just for the TypeScript checker though, because an omitted argument in JavaScript would be undefined anyway. If I wanted to, I could set the default for the parameter to any valid JavaScript expression. One thing about ES6 classes is that there's no need to declare properties outside the constructor. These lines are just TypeScript type annotations so I can remove name, phone, and email, and this code is still valid ES6. However, TypeScript doesn't like it and this is one of the small differences between ES6 and valid TypeScript. The good news is that there's a refactoring for property doesn't exist errors in atom-typescript. Let's say that we just wrote our constructor in the valid ES6 style and I don't want to have to manually create property declarations. When the cursor is over the error in atom-typescript I get a lightbulb in the left margin. Pressing Alt+Enter gives me some options. Since this is a simple property, the one I want is the second one, Add name to Contact. Hmmm, it added name as any, why? Well, I haven't specified a type in the function signature, so it's implicitly any. Let me fix that and do it again. Now it thinks name should be a string. In this course we're using TypeScript with the no implicit any option turned off. So we don't have to provide type annotations on function signatures. However, my position is that it's not a bad practice since it help document the intent of your code. This is entirely up to you and your team, and of course these type annotations are a TypeScript only syntax that is erased when it's compiled. I'll provide type annotations for the other two parameters, and then implement the property declarations using the lightbulb. Finally, I'll fix up the formatting with the built-in atom pretty printer by highlighting the class and pressing Ctrl+Alt+L, looking good.
Destructuring in a Constructor Signature
If you want to use an object to instantiate your class, destructuring works great with constructors. I'll comment out and copy the constructor and change the argument to a destructuring assignment for name, phone, and email. Let's leave email optional by assigning a default of undefined. Now I can change my new call to pass an object literal containing the property's name and phone and this'll work. If you're not sure what's going on here please review the clips on destructuring in this course. When a class begins to get complex passing in arguments as named properties on an object quickly becomes the only practical way to keep things straight with different permutations of optional arguments. And destructuring is a great way to accept such objects. I should mention that if you often find yourself copying properties of the same name from one object to another you may wish to look into the extend function that's available as part of JQuery, _JS, and other libraries. The extend function copies name properties from one object to another and can help simplify code like this.
Methods
A method is a function attached to an object. ES6 classes can implement methods to perform work and interact with data fields in the class. It'd be nice if our Contact class could say hello to someone. Let's implement a greet method. Greet, open/close parentheses, open curly, close curly. We haven't used the function keyword, but this code definitely implements a function. We can put Hello, my name is + this.name. I'll even change it to a string template using Alt+Enter. Similar to what we did inside our constructor function, methods in an ES6 class use the this keyword to refer to properties of the class. Here, this.name refers to the name property of the current contact instance. Methods can have parameters just like normal functions. I'll add a parameter greetee here as a string and change the statement to Hello greetee. Notice that I didn't have to prefix greetee with this, that's because it's a function argument and not coming from the class instance. We can call our method with console.log, .greet, Tim. When we run our program we see Hello, Tim, my name is Alice. ES6 class member are always public, unless you implement privacy manually using a closure pattern. The TypeScript type system extends ES6 classes by providing the ability to tag class members as public, private, or protected, but be aware that the private and protected levels only exist at develop-time. They're erased during compilation and the class members are made public. Any calls made to a private method outside its class will cause TypeScript errors, but will work at runtime. Just to show this I'll mark greet withy private, and we can see that TypeScript now raises an error about our call to put on line 17, Property greet is private and only accessible within class Contact, However, despite this error that emit succeeded and we can still run our program and see the greet message. If you wish to implement functions in JavaScript that are truly private at runtime you have a few choices. The first is to implement them manually using the reveal in module pattern, otherwise you could instead use an ES6 module, or a TypeScript namespace and just un-export the things that should remain private. With TypeScript we can explicitly mark class members public if we want to, and now our TypeScript error goes away, but since this is the ES6 default behavior anyway, the public keyword on greet is redundant.
Inheritance
ES6 supports class inheritance via the extends keyword. Let's say I want to create an employee class that inherits from Contact. We want our new employee class to have a name, email, and phone property, as well as a greet function, but we also want our employees to have an employee ID and a hire date. We can do this by writing class Employee extends Contact. Since I'm using TypeScript I can define the employee ID and hire date properties at type annotations so they won't exist on the class unless they're set, but I'll still get IntelliSense support for it in my editor. I can create a new employee by writing const pat = new Employee. And then I need to call it into a constructor. Since I didn't provide a constructor for employee the constructor is still the one on Contact. (Working) However, since Pat is an employee I can now set a hire date. New Date, 2015-01-01. I can print out the hire date by logging pat.hireDate.toUTCString. Notice how TypeScript gives me autocomplete on the date methods. I can also call into the greet function, which part of Contact, pat.greet Renee. Finally, I can check if Pat is an instance of employee and Contact by using the instanceof operator. Console.log, pat instanceof Employee, console.log pat instanceof Contact. Both of these will return true. One thing I want to point out about class declarations is that they're not hoisted. If I move the declaration of Employee above Contact, TypeScript will be happy. However, this will result in a Cannot read prototype of undefined error at runtime with TypeScript. Or, in the future a temporal dead-zone violation. So just be aware that the base class needs to be defined first. ES6 is not like some other languages where class definitions exist in ambient space. I'll move that back.
Calling Code from the Derived Class
In ES6 the parent class is referred to as super. In this clip we'll see how to call code on the super class from the derive class. Let's say we want to implement a custom constructor on Employee. When I create an empty constructor I get two errors. The one I'm concerned with for the moment is Constructors for derived classes must contain a super call. The super call is referring to the super constructor. Even if you haven't defined a constructor on the super class you must put a call to super in the constructor for the derive class. So let's call super. Okay, now I get the error, Supplied parameters don't match any signature of the call target, twice. Do you know why? I'm getting it once for my call here to new open Employee where I'm specifying this object literal, but my new constructor doesn't take any arguments. I'm getting the other one because I'm calling super, but the constructor for contact is expecting an object with at least a name and phone number. I'll add the arguments for employees and other destructuring assignment, name, phone, email, employeeID and hireDate. Then I'll pass a new object literal into the super call using the new ES6 object literal shorthand. That should satisfy the contact constructor. After the super call on the constructor I'll do the assignment to the employeeID and hireDate property so those'll get set too. Off camera I updated the constructor call for pat to include the hire date and pat's employee ID. We should be all set to run this. When I run this code everything still works as expected. Now let's say we'd like the greeting for an employee to contain some extra information, but we don't want to rewrite it from scratch. I'll create a method on Employee called greet and accept a greetee string. I can call the greet method on the super class by writing super.greet, greetee. Then I can take the result of that and add, By the way, I'm an employee. Running our code from the Command Line shows our new result. We got the greeting from Contact, plus our added text. When implementing ES6 classes I'd like to recommend that you avoid creating deep class hierarchies, try to keep it to in one or two levels at most. Deep inheritance chains can sometimes be made to work in languages like C# or Java, but in a dynamic language like JavaScript your code will be far more flexible if you prefer composition patterns. In particular if you're using TypeScript try to compose complex classes by implementing multiple interfaces. We'll talk about interfaces in a later clip.
Accessors
ES6 classes provide a convenient syntax for accessors, often referred to as getters and setters. Accessors aren't technically a new ES6 feature, but they work so well with ES6 classes I thought it would be useful to cover them. If you try to use an accessor with TypeScript in ES3 mode it'll raise a compile-time error. Getters and setters are specialized properties on a class that allow custom logic, but are more convenient then using functions. Let's say that in our company employee IDs allow any alpha-numeric character, but all letters must be upper-case. I'll mark the employeeID field as private and change it to _employeeID. The underscore doesn't have any special meaning to TypeScript, but it sometimes uses it a convention to notify a field. Now I can define a getter for employeeID. Get employeeID, open/close parentheses. This works just like a method so I can return this._employeeID. Now we can also create a set routine, set employeeID, value. The value here is the string that we want to pass in. I'll write this._employeeID = value.toLocaleUpperCase. Notice that I'm setting the private variable here, the one with the underscore. If you accidentally try to set the setter you'll get a Stack Overflow error because it'll call itself recursively. One last thing that I'll put or blank string on here in parentheses. This'll illustrate that an actual string will get used if the setter receives a falsey value like null or undefined. Notice that we're still setting this.employeeID in the constructor, so even if the employeeID is passed into the constructor as null, it'll be set as blank string. Let's see that. (Typing) And if I change it to abc123 lower-case, when I run it I see ABC123 with capital letters. One thing to note about getters and setters their values aren't included in a call to JSON.stringify. If I log JSON.stringify, pat, I don't get the employeeID accessor, instead I get the _employeeID field. A common thing to do with accessors is to omit a setter. That makes the corresponding property read-only. Attempting to set a property that has a getter, but not a setter is an error in strict mode. And technically the code inside an ES6 class always operates in strict mode, even it's not set in the containing scope. Just be aware that TypeScript doesn't give an error in this scenario.
Class Expressions
ES6 supports some interesting ways to declare and even pass around classes beyond what we've already covered. The first is declaring a class as an expression. Let's say that I have a class called MyWebService with a method called getData. For arguments sake let's say that getData makes some sort of an expensive call based on an ID. While testing our system we won't really want to call MyWebService and waste time and money, So perhaps instead we can make a fake version of MyWebService and test against that instead. I'll change this to Fake_MyWebService and just make this log out the id. So now I've got a new class, but how do I make code that uses MyWebService use Fake_MyWebService instead? Well one way is by converting these declarations to expressions. I'll create a variable here, const useFakes = false. Let's assume that we get that from a config file somehow. Then we can say if not useFakes var MyWebService = this thing, else var MyWebService = this other thing, close curlies. Now down here we can use our web service. Var webService = new MyWebService, WebService.getData, 5. If we run this we should get no result, and that's because our current implementation is just pretending to make an expensive call here. However, if I change useFakes to true and run it, we can see that we get the log message that we called the fake web service with ID5. Class expression support and immediately invoke style 2. This is convenient syntax for creating a singleton. I can put new before the class keyword and open and close parentheses after and now MyWebService will be an instantiated object instead of just a class. I'll do that for the bottom too and change the call appropriately. If my constructor required parameters I'd pass them in here. Now that I've got an instantiated class I don't have to call new on MyWebService anymore. And if I try it, yep, it still works and calls my fake web service that just solved the parameter. I should note that there's bug in TypeScript 1.6 where class expressions require an extra set of parentheses in order to work correctly at runtime. I added those off camera in this demo. This emit issue was fixed in TypeScript 1.7 in the extra set of parentheses are no longer necessary. One thing that I should mention is that you technically don't have to provide a name for the class to find in a class expression, but doing so can make stack traces more helpful, and it enables the use of static methods, which we'll discuss in the next clip.
Static Methods
ES6 classes support static methods and properties. Static methods and properties can be used without creating an instance of the class. Static methods are often used as utility functions, and static properties are often used for things like caching or tracking class-related metadata. One common example is keeping track of how many instances of a class have been created. I'll create a new class called TestStatic and define a method called doubleNumber. This method will take a number and double it. I'll tag the method with the static keyword. Since doubleNumber is static it exists on the class itself, not on instances of the class. So to call it I don't have to set up a new TestStatic first, I'll just call it directly. Console.log TestStatic.doubleNumber 10. When I run my code this returns 20. Let's pretend that I added some instance members to TestStatic, and now I'd like to keep track of how many TestStatic instances have been created. I can add a static member count here, and then if I add a constructor I can call TestStatic.count += 1. Notice again, I called count using TestStatic, not using the this keyword. This is for members on the instance. Since count is static it's not on the instance, it exists on the class itself. Now if I create two variables as new instances of TestStatic, and then console.log TestStatic.count, I'll see 2. I recommend using static methods whenever your code doesn't require accessing member variables, which with ES6 classes generally means methods that don't contain the this keyword. One more thing I should note is that creating a static property inside the class body with an initializer like this is only valid in TypeScript. Similar to how ES6 doesn't support ambient class property initializers, ES6 also doesn't support ambient static initializer statements like this. The valid ES6 syntax would be to put a line just outside the body of the class, TestStatic.count = 0, and that's how TypeScript will transpile this code when emitting to an ES6 target.
Abstract Classes and Interfaces
I'd like to wrap things up by talking about some TypeScript-specific features of ES6 classes. In this clip we'll discuss how to write a class that implements an interface, and we'll also discuss the new abstract keyword. TypeScript 1.6 added support for abstract classes and methods. Abstract classes are intended to be used as base classes for other classes. To enforce this TypeScript will provide a compile-time error if it sees an attempt to instantiate an abstract class. Let's say that we're writing an arcade game. We might create a class called Sprite that represents an object on screen. Sprite might have an x and a y property that stores the Sprites location, and an image URL property that holds the source, the graphic. I could then create some classes that extends Sprite. Let's call one Player and the other Monster. I'll instantiate Player, const p = new Player. We can see that p has the x property and TypeScript knows it's a number, nothing unusual here. As the designer of this system I might not intend for developers to be able to create instances of Sprite directly, but that's currently possible. Const s = new Sprite. There's no complier error from doing that because it's just a regular class. To ensure that creating a Sprite is not possible, in TypeScript I can mark Sprite as abstract. Now I get the error, Cannot create an instance of the abstract class Sprite. In our game we'll call an update method on the Player and Monster objects on every frame. This'll allow the Player object to check for input and will allow the Monster objects to run AI. It'd be nice if we could have TypeScript force any class that implements Sprite to have an update method. We can do that by declaring the update method on Sprite and marking it abstract. Abstract methods can't have an implementation, in fact they can't even have curly braces, or it'll be an error. Now we get two errors, one for each of our classes, Non-abstract class doesn't implement inherited abstract member update. I'll implement update in Player and check for input, and we'll also implement update in Monster and have our Monsters run their AI routine. Another way to standardize structure between classes, or really any kind of object in TypeScript, is with interfaces. Interfaces are very flexible because they're not tied to an inheritance chain. Any object can be marked to implement any interface, and on my class hierarchies interfaces are purely an artifact of the type system, which are erased at compile time. I'll define an interface to represent a Sprite, interface ISprite, x: number, y: number, imageURL string, update function that returns void. Now I can mark Sprite with implements ISprite. So far so good. Now let's say I add a new string property called name to ISprite. TypeScript informs me that Sprite incorrectly implements the interface. I can add name = blank string to Sprite, and TypeScript is happy again.
ES6 Classes
Well, that's all I have to say about ES6 classes, and in fact, we've reached the end of Using ES6 with TypeScript, so let's wrap things up. First, I'd love to hear what you thought about Using ES6 with TypeScript, so please leave a comment on the course website. If you're on Twitter reach out to me @NYCdotNet. In this course we focused on the syntax features of ES6, including an overview of the new general syntax features and a deeper dive on both modules and classes. We didn't really touch on the changes to the built-in JavaScript objects though. If you want to use the new built-in objects I highly encourage you to check out the core-js poly-fill library, which'll allow you to take advantage of most of the updated ES6 built-in object features in today's ES5 environments. That project is available on GitHub at the cURL. There're several Pluralsight courses that make a great follow-up to this one. First off, if you liked this course you'll love Practical TypeScript Migration, that's my first Pluralsight course, which walks you through the process of converting a website from JavaScript to TypeScript, start to finish. There's a ton of great information in there and, of course, the best is you get to listen to me. If you use Angular, Deborah Kurata and Justin Schwartzenberger both have excellent courses that deal with TypeScript-specific issues in that environments. If you want to learn more about the module loaders I showed, Jeff Valore has a course on RequireJS, and Web Higbee has a course on SystemJS. I didn't talk at all about minification or bundling in this course, and those are key parts of modern JavaScript delivery in the browser. Two of the most popular bundlers that work with the CommonJS format in the browser are Browserify and webpack. Joe Eames and Jeff Valore have courses about those tools. Finally, if you'd like to learn more about hapi, which I used as web server in this course, check out Ryan Lewis's Building Web Applications with hapi. For me and my family I want to say thank you so much for watching, and good luck with TypeScript and ES6, goodbye.
Course author
Steve Ognibene
Steve Ognibene is an application developer in New York City specializing in SQL Server, C#, VB.NET and TypeScript. Steve is an active contributor to open source projects and is the author of the...
Course info
LevelIntermediate
Rating
(108)
My rating
Duration2h 15m
Released25 Feb 2016
Share course