What do you want to learn?
Leverged
jhuang@tampa.cgsinc.com
Skip to main content
Pluralsight uses cookies.Learn more about your privacy
Advanced TypeScript
by Brice Wilson
TypeScript is a modern language with many advanced features. This course will teach you those features that build on the fundamentals you already know and allow you to harness its full power to write better code with fewer errors.
Start CourseBookmarkAdd to Channel
Table of contents
Description
Transcript
Exercise files
Discussion
Recommended
Course Overview
Course Overview
Hey everybody, my name is Brice Wilson and welcome to my course, Advanced TypeScript. I'm a server and client-side web developer. I love TypeScript and taking advantage of everything it has to offer. In this course, we're going to explore some of the more advanced features of the language that will help you solve difficult problems with new techniques and generally take your TypeScript code from good to great. Some of the major topics we'll cover include intersection and union types, type guards, decorators, asynchronous patterns, and linting. By the end of the course, you'll know how to use the language's most powerful features to solve difficult problems using clean and compact techniques you might not have even thought possible with TypeScript. Before beginning this course, you should be familiar with the basics of JavaScript and TypeScript but you certainly don't need to be an expert at either of them. I hope you join me on this journey to learn lots of powerful TypeScript techniques with the Advanced TypeScript course at Pluralsight.
Maximizing TypeScript
Introduction
Hi, this is Brice Wilson with Pluralsight. Welcome to Advanced TypeScript. In this course, I'm going to help you take your TypeScript skills to the next level. Maybe you've experimented with some basic features of the language and want to explore some of its other capabilities, or maybe you've been using it for a while and find that you now have a need for some of its more advanced features on your project. In either case, I think this course has just what you're looking for; and we'll cover lots of powerful features you won't normally see in an introduction to the language. This course is really a follow-up to my earlier course titled TypeScript In-depth. That course was for complete beginners with no TypeScript experience. In it I covered all of the basics needed to get up and running with TypeScript and presented lots of beginner and intermediate language features like functions, classes, interfaces, modules, generics, and lots more. That course is certainly not a requirement for this course, but if you're new to the language; or just need a refresher on some of those topics, then that would be a great place to start. I also used the same simple demo project in both courses, so I hope that makes moving between that course and this one a little more seamless.
From Beginner to Advanced
I want to briefly make the case for obtaining advanced skills. Learning something new in my experience, seems to follow a very consistent pattern. Initially it can be very frustrating. Everything is new and even the most basic things may not make much sense. However, stick with it and usually within a reasonable amount of time you'll have that aha moment when everything suddenly makes sense. You're no longer struggling with the simple stuff, and you start to see the big picture. This is usually followed by a period of tremendous productivity. You're proud of what you learned, you're applying it on a project, and you're getting work done. This all feels great, and you should definitely enjoy it and build lots of cool stuff. However, there are warning signs to watch out for. Being proficient is usually good enough to be productive, and being productive feels so good that it's easy to stop pushing yourself to learn more and continuously get better. Just like athletes have to keep training and pushing themselves to reach new levels of fitness, you have to keep pushing yourself if you want to make that jump from proficient to advanced. I think the extra effort is worthwhile. Learning the advanced features of any given technology often opens up new ways of looking at problems and all of your new advanced skills add up to new solutions you may not have even thought possible if you stayed in your comfort zone of mere proficiency. Push yourself to go from proficient to advanced, and the things you'll build will be even better. That's what I hope to help you do with this course. Let's now take a quick look at what I'll cover in each module to help you in that journey.
Course Overview
In the next module I'll show you how to do more with the basic types you're already familiar with. I'll show you some new tricks with arrays and how to use existing types to create union and intersection types. After that I'll show you some more advanced type features, such as how to create polymorphic disk types and type guards. I'll then have an entire module dedicated to creating and using decorators. They're a very powerful feature and are used heavily in popular frameworks like Angular. After that, I'll show you several ways to manage asynchronous code in TypeScript. This will include using traditional call-back functions, as well as promises and the much newer async and await keywords. I'll then wrap up the course with a short module on a popular linter for TypeScript. Linters check your code for errors and adherence to coding standards. TSLint is easily the most popular TypeScript linter and does a great job of helping you write better code. The last thing I want to do in this quick introductory module is a quick demo to show you the app I'll be working with throughout the course and the tools I'll be using to run it.
Demo: Project Structure
In this demo I'll show you how the code and the demo app is structured and point out the tools and configuration I'll be using to run the app. I want to start by showing you the software I'll be using in the course. I'm here on the official TypeScript page because I'll obviously be using TypeScript. Since this is the advanced TypeScript course, I'll assume you already have TypeScript installed; but in case you don't, this is one of the places you can get it. I also want to point out that they redesigned their site not too long ago, and it looks great and has lots of good documentation and other helpful stuff. In my next browser tab I have the Node website open. I'll be using Node to run the code I write in the course. I want you to be able to focus on the specific language features I'll be covering, so the demo app is not a full-blown client web application. It's a very simple command line app, and Node is a great tool for running programs like that. In my last tab I have open the site for Visual Studio Code. It's the editor I'll be using in the course. TypeScript works great with lots of different editors, so using Visual Studio Code is certainly not a requirement. I like it because it has excellent support for TypeScript. It supports lots of different languages and extensions. It runs on MAC, Windows, and Linux; and the team working on it puts out frequent updates. In my earlier course, TypeScript In-depth, I show you how to configure other editors like Sublime Text and WebStorm for TypeScript development, in case you happen to prefer one of them. Okay, I'll now jump over to Visual Studio Code and show you how the project is structured. I tried to keep all of this pretty simple. All of the code is in a folder named Library Manager. The idea is that it's an application that could be used to manage things in a typical library. This includes books, magazines, librarians, and things like that. You can see most of the files in the project here on the left side of the window. The main file for the application is app.ts. It's the starting point for the app and imports code from other files as needed. Some of the other files already contain code that I wrote in the earlier course. There are several interfaces and classes, for instance for books and librarians, that I'll be reusing in this course. Inside the .vs code folder are a couple of files I'll be using to build and run the code I write. The task.json file contains the configuration for a Visual Studio Code build task. All this task is going to do is run the TypeScript compiler. You can see here that the command property is simply set to TSC. So if you're not using Visual Studio Code, you can do the same thing this build task does by just going to your terminal and typing TSC in the project directory. The command inside the build task is so simple because I'm keeping all of my TypeScript compiler options in a tsconfig.json file. I'll open that and show you what it looks like. It includes a section for the compiler options at the top. The files property at the bottom tells the compiler to just compile the app.ts file. Other files will get compiled as necessary based on what's being imported. Notice also that I'm running the compiler in watch mode so that I don't have to start it over and over with each small code change. Throughout the course I'll be tweaking other options in this file in order to make everything work with the features I'll be demonstrating. All of the JavaScript output by the compiler will be put in the JS folder. So that's where I'll direct Node to find the files it needs to run. There's a separate file inside the .vs code folder that lets me configure Node to run the application from inside Visual Studio Code. It's named launch.json. I'll open it and just show you a few of the options I've set in it. Everything in here configures what will happen when I press F5 to run the app. The type is set to Node and the program property is set to the app.ts file in the root of the project. Node doesn't run TypeScript files directly, so it actually finds the corresponding JavaScript file using the outDir property a little further down, which points to the js folder of the project. Don't worry if you're not using Visual Studio Code and this all looks a little cryptic. I'll show you how to easily run the app from the command line in just a second. The last property I want to point out in this file is the console property that I currently have set to internal console. I'll be directing all of the output from the app to a console window, and this just tells Visual Studio Code to use its built-in console for that output. That will just keep me from having to jump back and forth from an editor to a terminal to show you the code and what was output. In earlier versions of Visual Studio Code, this property was called external console; and the value could either be true or false. You may see that in the course download materials as well, but it does the same thing. Okay. I'll now show you how I'm going to run the app. The app.ts file currently just contains a simple console.log statement. I'll press Command + Shift + P to open the command palette in Visual Studio Code. I'll then type build to search for the command to run the build task. I'll click on the command to run it. That starts the compiler running in the background. I can now press F5 to run the app with Node. That causes Visual Studio Code to open its debug console at the bottom of the window, and you can see the output from the app.js file inside it, Welcome to Advanced TypeScript. If you're not using Visual Studio Code and want to run it from a terminal, you can just change into the js directory inside the project and then type node app.js. It outputs the same message. Okay, that's it for the course overview. In the next module I'll show you how to do more with some of the basic TypeScript types you already know. Keep watching.
Going Further with Basic Types
Introduction and Overview
Hello and welcome back. I'm Brice Wilson. In this module, I'm going to show you how to do more with the basic typescript types you are already familiar with. I will cover a couple of nice ES 2015 features typescript supports and then show you several ways to take the types you already know how to create and combine them in new ways to create entirely new types that ultimately allow you to write more flexible code. I will begin by showing you how to perform destructuring assignments and how to use the spread operator. These are both new ES 2015 syntax features that the typescript has adopted. After that, I will show you how to create and use tuple types, union types, and intersection types. They may sound like completely new things but they are really just different ways to combine existing types to make your code more flexible and expressive. After explaining intersection types, I'll show you how to create mixins which allow you to combine all of the members from multiple classes into a single class. I'll then show you how to create string literal types and how to define type aliases that can make your code more readable as you begin to use some of the other features like union types and intersection types. I'll start things off in the next clip by showing you how to perform destructuring assignments.
Destructuring Arrays and Objects
In this section, I'll show you the new syntax available for destructuring arrays and objects. Let's start with a definition. A destructuring assignment is the process of assigning the elements of an array or the properties of an object to individual variables. You've always been able to do this of course but there is now syntax specifically designed to do this for you in a clean and concise format. Let's look at a couple of examples. Let's imagine I have a simple array of strings named medals. It contains three elements with the values gold, silver, and bronze. If I wanted to put each of those values into distinct variables, I could just declare a new variable and index into the array to get the value to assign to the variable. This works fine and the result will be that the variable named first will have the value gold. Second will have the value silver and so on. The new destructuring syntax lets me perform those same declarations and assignments in one line of code. The new variable names are listed inside square brackets on the left side of the assignment and the array to be destructured is on the right side. The values in the array elements will be mapped to the variables in the corresponding array positions on the left. For instance the value in the first element of the medals array will be assigned to the variable in the first position of the array on the left side of the assignment. You might wonder how to deal with any extra values if the array you are destructuring contains more values than you expect. That can be handled with the new rest parameter syntax. I'll show you how to use that in a demo in just a minute. Let's first see how to destructure objects. Destructuring objects is nearly identical to destructuring arrays except that instead of assigning elements of the arrayed individual variables, you are assigning properties of the object of those variables. For example let's say I have an object literal named person that has name, address, and phone properties. I could easily declare new variables and use that dot syntax on the object to get the value of the property and assign it to the new variable. However, with the new syntax I can accomplish the same thing with this much simpler code. It looks similar to the array example earlier but notice that the new variable names are listed inside curly braces instead of square brackets on the left side of the assignment. The right side simply contains the object variable. Also notice that the names of the variables match the names of the properties on the object. That's how it will be determined which property on the object will match to which variable since the properties on an object aren't ordered like the elements in an array. There is a slightly different syntax you can use if you want to use variable names that don't exactly match the property names on the object. I will show you how to do that in the next demo.
Demo: Destructuring Arrays and Objects
In this demo, I'll show you how to destructure arrays and objects into individual variables and show you how to use rest parameters. I'm going to start here in the app.ts file. It is currently empty except for a few import statements at the top. Notice that I'm pulling in the book and magazine interfaces from the interfaces module. I'm getting a couple of classes from the classes module and I'm inputting everything in the utility functions module which I'll refer to here with a prefix util. I'm going to quickly write a simple function name PrintBookInfo that takes a book as a parameter and just prints the title and author to the console. I'll use it to output the books I'm going to destructure. I'll now make a call to the getallbooks function in the utility functions module. It returns an array of books and I'm going to destructure that array and assign the first two books in the array to a couple of new variables. Book1 and book2. I declare the variables with the let keyword like any other variable but by putting them inside the square brackets, typescript knows I want to destructure the array on the right side of the assignment. I will call the PrintBookInfo function on both of the books so we can see if they were extracted from the array as we would expect. I'll use the command palette in Visual Studio code to start the build test that compiles the code to JavaScript. I can then run it inside Visual Studio code by pressing F5. The output is printed to the debug console and you can see that it did correctly print the first two books in the array. I'll now stop that and return to the code. I now want to show you how you can preform a destructuring operation with function parameters. I'm going to remove these calls to PrintBookInfo and temporarily comment out this assignment to book1 and book2. I'll write a new function named LogFavoriteBooks that takes an array of books as a parameter and destructures the array into variables I can use inside the function. Notice that the syntax here is very similar to the earlier assignment. I've specified that the type of the parameter will be a book array but it will be assigned to the two new variables inside the square brackets. Inside the function, I'll again just call PrintBookInfo on the two books. I will now call a LogFavoriteBooks and pass it the array of books returned from GetAllBooks. I'll quickly run that and you can see I get the same results as before. So the destructuring of the parameter in the LogFavoriteBooks function isn't doing anything magic. I could've just extracted the first two books from the array inside the function but this way did save me some typing and makes my code a little more compact. However, the array returned from GetAllBooks actually contains more than two elements and as the code is written right now, I don't have a way to easily reference anything beyond those first two elements. That's where rest parameters are helpful. They are called rest parameters because they let you access the rest of the array. I'll destructure the remaining elements in the GetAllBooks array into a rest parameter named others. The syntax is important here. You specify a rest parameter by prefixing it with three dots followed by the name of the variable you want to use to store the values. Since the whole point of a rest parameter is that it will receive an unspecified number of values. The variable will actually be an array of those values. In my example here, the first two elements of the array will be destructured into the book1 and book2 variables that will have the type book. The remaining elements will be stored in a new array of books named others. Just to show you what that looks like I will log the others array at the end of the function. I'll hover over the variable and you can see that it should contain an array of books. I'll run the code again and have a look at the output. Okay it looks like what I would expect. The first two books are printed out just as they were before and the remaining books in the array were just logged as JSON. The call to GetAllBooks only returns four books right now. I'm now going to comment out the call to LogFavoriteBooks just so we don't have to see that output every time. I'm also going to uncomment the line above that creates the book1 and book2 variables. I now want to show you how I can destructure the properties on an object. I'm going to use the book object in the book1 variable. Let's say I want to store the title and author of the book in their own variables. To do that I declare the variables inside curly braces and assign them the book object. When I destructure the array above I declared the new variables inside square brackets, but when destructuring an object the new variables are declared inside curly braces. I'll quickly log the two new variables. Before I run this, I'll hover over the title variable and you can see the typescript is treating it as a brand new variable with a type string. It notes that the book object on the right side of the assignment contains a property named title and it is going to map it to the new variable with the same name. It will do the same thing for the author variable. I'll run it and you can see it prints the title and author of the first book. Let's now suppose I want to destructure the object into variables that don't exactly match the names of the properties on the object. I can do that with a slightly different syntax. Inside the destructuring, you list the property followed by a colon and the name of the variable that should receive the value in the property. I'll change this code to store the title in a variable named book title and I'll store author in book author. The compiler is now complaining that it doesn't recognize the variables in my log statement so I'll need to update them to the new names. Now when I hover over title in the destructuring you can see that it is not being treated as a new variable. Typescript is reporting that it is simply a property of a book. If I slide over and hover over book title you can see that it is now the new variable that will receive the value. I'll run it again and you can see I get the same results. I'll comment out that code for now and show you how I can use the same object destructuring on the parameter pass to the PrintBookinfo function above. I'll change the item variable to be the same destructuring I just used inside curly braces. The book pass to the function will be destructured and I'll have the title and author in separate variables. I need to change the log statement to use those variables instead of referencing the properties on the item object. Just to show that it still works, I'll quickly call it and pass the book in the book1 variable. The output is the same except this time it printed the variables containing the destructured values. Okay let's now go talk about the spread operator.
The Spread Operator
The spread operator is another new bit of syntax typescript adopted from ES 2015. The spread operator gets its name from its ability to spread the elements of an array across the elements of a new array or even a set of function parameters. In this simple example, I have an array of newBookIDs. It just contains two elements with the values 10 and 20. I'll then add them to the end of this array named allBookIDs using the spread operator. To do that, I just prefix the name of the array with three dots. The elements in that array will then be spread across new elements in the current array. Since the array already has three elements, the two elements in the newBookIDs array will be spread across the fourth and fifth elements resulting in an array that looks like this. You can also use the spread operator to spread elements across function parameters. I'll show you how to do that in the next demo.
Demo: Using the Spread Operator
In this demo, I'll show you how to use the spread operator to pass function parameters and declare array literals. I'm going to start here in app.ts by using a code snippet to paste in a new array of books. I'm assigning it to a variable named schoolbooks and it is just an array with three object literals representing books. I want to show you how I can use the spread operator to add the elements in this array to another array. I will sign the array of books return from a call to GetAllBooks to a new variable named BooksRead. I'll now call the push function available on all arrays to add all of the school books to the booksRead array. You can see here that the push function is defined with the rest parameter. That required for me to call it with a spread operator and not have the compiler complain about the function signatures not matching. I'll use the spread operator which are the three dots and the schoolbooks array is the parameter to the push function. The elements in the school books array will be spread across the rest parameter and added to the booksRead array. I'll log the updated array so we can see that the books got added. If I quickly run the code, you can see that the BooksRead array now contains the new elements from the schoolbooks array. I've got to scroll a bit to see all of them. I will show you how you can use the spread operator to append items to array literals. I'll comment out the log statement just so we don't see that output anymore. I'll declare a new of array of strings named poets and assign it in array of the names of several poets. I'll declare another string array named authors. I'll also assign it an array literal. The first two elements will be author names but then I'll use the spread operator to spread the elements from the poets array across the end of this array. I again just used the three dots in front of the array variable name. I'll log the authors array, run it, and you can see that the array does in fact contain the names of the poets in the last three elements. The spread operator is a pretty simple but handy feature. Let's now go take a look at tuple types.
Tuple Types
In this section, I'm going to present tuple types. They are a special type you can create that can be a useful building block for other types. The typescript language specification defines a tuple type as a type that combines a set of numerically named properties with the members of an array type. They weren't very much like arrays but have a couple of unique features. Their extensions to arrays were the type specified for a fixed number of elements and the elements may contain different types. In this example, I've declared a new variable named my tuple that is a tuple type and I'm assigning it a literal value. The type is specified after the variable name like any other type annotation. Tuple types list the type that is allowed in each element of the array inside a pair of square brackets. The first element of this array must be a number and the second element must be a string. I can manipulate the values in the tuple type using indexes like I would for any other array. If I were to try to assign a string value to the first element in the array, I would get a compiler error since the type specifies the first element must be a number. Similarly, I'll get an error if I try to assign anything but a string to the second element. Things are a little different for elements in the array beyond those with explicitly specified types. They may contain any of the types listed as part of the tuple type. In this example, that means the third element can contain a string or a number since those are the two types in my tuple type. Let's now jump into a demo where I will show you some additional examples.
Demo: Creating and Using Tuple Types
I'll now show you an example of using tuple types as a way to describe key value pairs in typescript. I'm still here in app.ts and I'm going to create a simple tuple type to represent the location of a book on one of the library shelves. I really know nothing about how library catalog systems work so please forgive me if you do and my catalog location makes no sense. I'll declare a new variable named catalog location and specify that it will be a tuple type where the first element is a string which will represent the shelf location and the second element will be the actual book at that location. I'll assign it an array literal where the first element is a crazy string representing the location and the second element is the book stored in the book1 variable I created above in an earlier demo. This syntax is valid and I'm not getting any complaints from the compiler because the types in my value array match the types on my tuple type. There is a string in the first element and a book in the second element. If I change the value in the first element to be a number, I immediately get an error. I'll hover it and the compiler lets me know that types of property zero are incompatible. Type number is not assignable to type string. This makes it obvious that the compiler was expecting a string and I'm giving it a number but I want to explicitly point out that it is referring to the property named zero. If you recall the definition of a tuple type I showed you in the slides, it is a type that combines a set of numerically named properties with the members of an array type. Property zero is the numerically named property for the first element in the array. I'll show you another example of using these properties in just a minute. I'll change the value in the first element back to a string to get rid of the air. If I change the value in the second element to something other than a book, I get a similar error but this one refers to the incompatible types for the property named one since it is the element with the index of 1 in the array that has a type mismatch. I'll change that back to the book variable. I can additional elements to the array but the type stored in those elements must be one of the types to find on the tuple type. Therefor if I try to assign a Boolean to the third element in the array I'll get an error. However, if I change it to a string the error goes away because the string is one of the types in my tuple type. You may have noticed that my tuple type closely resembles a key value pair. The first element is a string and could be the key for the second element which is a value, a book in my case. Creating key value pairs are an excellent use for tuple types and is one of the examples provided in the typescript documentation that I want to show you now. I'll comment out this last bit of code so I can write a better version of it. I'm going to create the exact same interface used as an example in the typescript documentation. It is a generic interface named key value pair. It takes two type parameters named K and V. K represents the type of the key and V represents the type of the value. Because tuple types are extensions to arrays, this interface will extend an array that contains elements with the types assigned to K or V. This is the syntax for a union type, which we will look at more closely later in this module. The next step in creating this custom tuple type is to define the numerically named properties we saw earlier. I'll define on the interface a property named zero that will have the type assigned to K. I will define another property named one that will have the type assigned to V. I'll now rewrite my earlier example of a catalog location using this new custom tuple type. The variable will be a key value pair and the type parameters will be string and book. I can assign a value to it exactly like I did before. The first parameter is a string which maps to the type parameter K which must be the type of the property named zero. I'm not getting any errors because I've correctly assigned a string to that property. The same applies to the book I am using for the value in the key value pair. If I change either of the values so that they no longer match their assigned types, I will get an error. I can add new elements to the array as long as they match one of the valid types. Boolean still won't work since this tuple type is specified to contain strings and books, but all is well if I assign a string to the third element. Tuple types can be handy as a way of composing new types from existing types and I think this key value pair example from the typescript documentation is the best example of that. Let's now go back to the slides and talk about union types and intersection types.
Union Types and Intersection Types
In this section, I'm going to show you how to use union and intersection types. They are both a way of describing a type in terms of other types. I'll also show you how to create a mixin which is a technique for implementing an intersection type. Let's start by taking a look at union types. Union types provide you with a way to specify that a value will be one of several possible types. In this example, I specified a union type for the function parameter. It can accept a string or a number. I could have simply said that the ID parameter would have the any type but colors would obviously be able to pass anything to it. If I know that number and string are the only valid types it is better to restrict it to just those types so that passing anything else will result in a compiler error. The list of valid types are separated by a vertical bar. It resembles the logical or operator which is two vertical bars. I think that provides a nice hint as to its purpose in an union type. The type of the value can be a string or a number. I only listed two possible types for this parameter but you can list more separated by additional vertical bars if you need more. Intersection types are similar to union types except that instead of describing a value as having one of several possible types, an intersection type specifies that a value will have all members from multiple types. In this silly example, I've got a function named CreateCoolNewDevice. That returns an intersection type. The return type will have all of the members from both the phone and tablet types. The types that combine to form an intersection type are separated by an ampersand. This is your hint that you are looking at an intersection type. The logical and operator is represented by two ampersands. Intersection types are just separated by one but they contain all of the members from the first type and the second type.
Demo: Using Union and Intersection Types
In this demo, I'll show you some examples of using union and intersection types in the library manager app. Before I start creating the new types, I want to get some books and magazine objects so I can use in the examples. I will create a new variable named allBooks, and assign it the array of books returned from the GetAll books function. I will do the same thing for magazines. There's another function in the utility module named GetAll magazines. I'll assign each return value to all magazines. I'll now declare a variable named reading material. I want to be able to assign it either a book or a magazine. I could declare its type to be any. That would let me assign any value even a number like this that doesn't really make sense for how I intend to use the variable. However, it does satisfy my need to assign it both magazines and books. As you can see, I can assign it the first element of the allmagazines array without any errors. I can do the same thing with the allBooks array. The any type is really too forgiving. It allows me to do what I want but doesn't prevent me from doing things I shouldn't. I could change the type to book and it obviously won't let me assign it a number but it also won't let me assign it a magazine. The answer to this problem as I'm sure you've guessed is to use a union type. I'll use the vertical bar between the types I want to allow for this variable. When I add the magazine type to the union type you can see the error goes away. I can also assign it a book without any errors. Union types can also be used as the type of function parameters. I'll quickly write a new function named printTitle that accepts the union type. I declare the type of the item parameter just like I did the reading material variable above. Inside the function, I'll just log the title of the item since both books and magazines have a title property. I can call the function by passing it a book like this. I can also pass it a magazine. However if I try to pass it a string literal, I'll get a compiler error letting me know that I can't assign a string to a union type including book and magazine. Okay I'll get rid of that line and comment out the previous two. I'll now declare a new variable that will be an intersection type including book and magazine. I'll name it serial novel. Intersection types must include all of the members from the types in the intersection. Therefore if I try to assign a book to this variable I get an error. That's because the book type doesn't include a publisher property. Magazines have a publisher property which means anything I assign to this intersection type must also include that property. I'll change this and use a code snippet to assign an object literal to the variable. This object contains only the properties on the book interface, so I get the same error I got before. However, if I just add a publisher property to the object. You can see that the error goes away. The value I'm assigning now includes all of the properties from all of the types included in the intersection type.
Demo: Creating a Mixin
Mixins are effectively an implementation of an intersection type. In this demo, I'll create mixins that add some additional functionality to the university librarian class in the demo app. I'm going to start here in the classes.ts file and create the mixins I want to add to the university librarian class. Mixins are really just classes whose functionality you add to some other class. I'll first use a code snippet to paste in a new class named employee. It has a string property to store the employee's title as well as functions named add to schedule and log title. I have them each logging a simple message to the console. I use another code snippet to paste in a class named researcher. It contains a single function named do research which accepts the research topic as the strength. Before I do anything else, I am going to jump down to the end of this file and add each of the new classes to the list of things being exported from this module. Okay I now want to add the functionality from both of these new classes to instances of my university librarian class. In practical terms, I want to inherit the functionality from employee and researcher. However typescript doesn't allow you to inherit from more then one class at a time. The technique I'm about to show you let's you work around that limitation. The syntax here is probably going to look a bit strange but stick with me and I will make sense of it. The university librarian class currently implements the librarian interface. I'm going to add a comma after that interface and add the employee and researcher classes to the list of interfaces university librarian should implement. The thing that may seem immediately wrong about this is that employee and researcher are classes and I'm using them here like interfaces. Classes are normally things you extend but here I'm specifying that I will implement them. The typescript compiler doesn't complain about this and is willing to treat the classes as interfaces. However that means that the university librarian class is responsible for implementing the members on those classes even though they obviously already have implementations. I'm going to write a function in just a minute that copies the implementations from those classes into the university librarian class, but for now I just need to add the member definitions to satisfy the compiler. The red squiggly here is because I'm not fully implementing the members from employee and researcher. I will come down to the end of the class and use another code snippet to paste in very basic definitions for those members. You can see that I've pasted in the title property and the two functions from the employee class as well as the do research function from the researcher class. I'm not providing any implementation here. I've just defined the members and given them types and function signatures to match those and the source classes. I'm going to write a bit of code to copy the original implementations into instances of the university librarian class. I will do that back in the app.ts file. The first thing I need to do in this file is add employee and researcher to the list of things I'm importing from the classes module. I'll now come down to the end of the file and paste in the function that will add the implementations and my mixins to some other class. This is the same function you will find in the typescript documentation that shows you how to implement mixins. It is a short function but it performs the magic of adding the additional functionality to the classes we specify. It takes two parameters. The first is the constructor function or class to which you want to add functionality and the second is an array of constructors containing the functionality you want to add to the first. The function just iterates over the items passed in the second parameter gets the members from those classes and added them to the prototype of the class passed as the first parameter. To apply members from the employee and researcher classes to instances of the university librarian class I just call the function and pass university librarian as the first parameter and an array containing employee and researcher as the second parameter. The actual implementations will be added to university librarian when the function is called. But I'll get code completion support for all of the new members because I specified the class would implement the members from employee and researcher. Now when I create a new instance of the university librarian class and type a dot on the new instance, you can see all of the members from the mixins along with the members defined directly on the university librarian class. I'll call the doResearch function on this instance just to show you that the apply mixins function above did in fact copy the actual function implementations from the original classes. I'll run the code and I get the simple output message I generated in the researcher class. Mixins can be a nice way to combine functionality from lots of small classes into a carefully crafted larger class with just the functionality it needs. Let's now wrap up this module with a look at string literal types and type aliases.
String Literal Types and Type Aliases
The last couple of topics I want to cover in this module are string literal types and type aliases. They are both relatively small features but I really like them and think they can help you write simpler more readable code. String literal types are exactly what their name implies. A string literal treated as a distinct type. I realize that sound strange but I think you'll see their potential usefulness as I show you how they can be used. They are specified just like any other type annotation. At first glance, you might think this category assigns the string manager to the emp category variable but if you look close you can see that there is a colon after the variable name indicating that the next bid is the type of the variable. Variables declared with a string literal type can only contain the strings specified in the type. Therefore, my emp category variable can only be assigned the string manager. If I try to assign it any other string like non-manager the typescript compiler would generate an error. So far these types surely don't sound very useful. Their value becomes more apparent when they are combined to form a union type like we looked at earlier. Notice the vertical bar between the two types indicating this as a union type. I've unioned together two string literal types so that the variable can contain either of two possible strings. Manager or non-manager. Using a single string literal type is not particularly useful but unioning them together like this gives you enom like behavior that can be useful when you want to limit some value to a finite set of strings. I will now use the same union type to show you how type aliases work. Type aliases are just a way to refer to a type by a different name. You probably wouldn't decide you need a new way to refer to numbers or Booleans but you might decide an alias would be helpful for more complex types like the union type from the last slide. Unioning together two string literal types is long hard to talk about and generally kind of clunky feeling. I think that makes it a perfect candidate for a type alias. I'll create an alias for it named employee category. You create an alias using the type key word followed by the alias you want to create. You then set it equal to the original type. With that in place, I could rewrite the variable declaration above like this. Rather than writing out the entire union type, I just give it the employee category alias for that type. I think that's much easier to read and works just like the original. I can assign the value manager or non-manager to the variable and those are the only acceptable values. Trying to use any other strings will result in a compiler error. Let's now go experiment with string literal types and type aliases more in a demo.
Demo: Using String Literal Types and Type Aliases
In this demo, I'll create a new string literal type and define several type aliases for types I've used throughout this module. I'm first going to use a string literal type to describe how often a magazine might be published. I'll create a new variable named frequency and I'll combine two string literals into a union type to limit the possible values the variable can have to monthly and annually. I can assign values to the variable but only if they are one of those two strengths. No complaints from the compiler if I assign it the string monthly. It also accepts annually. However, if I change annually to semi-annually then I get the dreaded red squiggly under my variable name. Type semi-annually is not assignable to type monthly or annually. I'll change it back to annually to get rid of the error. The primary benefit of a language like typescript is your ability to express types for things based on what you know about the range of acceptable values. The compiler then alerts you if you try to use values outside that acceptable range. Being able to limit the type of a variable to a string is nice, but if you know that variable should only ever contain one of a small number of strings then it is even better if you can have the compiler check your usage against that small set. That's the benefit of using string literal types. Unioning these two string literal types together produces a new type I might like to use elsewhere in the application. So I think I will create a type alias for it to make it easier to type and a bit easier to read. I'll comment out this variable declaration and then use the type keyword to define a type alias named frequency. I'll set it equal to the same union of string literal types I used above. Let's now suppose I needed a new function that would return a magazine based on how frequently it is published. I'll stub one out named getMagazinebyfrequency. I'll pass it the preferred frequency and use my new type alias to describe the type of that parameter. As you can see, once you've declared a type alias you can generally use it like any other type annotation. I'll now create a couple more type annotations that I can use to make some of my earlier code a little more readable. I will create one called PrintMaterial that will be an alias for the union of the book and magazine types. I'll create another one named serial that will be an alias for the intersection of those same types. I can now go back to the places above where I used those types and refactor them to use the new aliases. I declared this reading material variable to be the union of book and magazine so I'll change it to print material. I can do the same thing for the parameter I'm passing to the PrintTitle function just below that. Finally, I can use the alias I named serial on this variable whose value must be the intersection of a book and magazine.
Summary
Whenever you learn a new piece of technology, you are usually focused on the fundamentals and understanding the basic used case for the technology. Initially you will probably only use the basic features. It is easy for your growing comfort with those features to keep you from ever exploring more advanced features and capabilities. In this module, I tried to take some of the basic typescript features you already know and show you some of their capabilities to go beyond what you probably learn in your early experimentation with the language. We all know how to declare a variable and assign it a value but I showed you how you can use destructuring assignments with arrays and objects to accomplish the same thing with less code. I also showed you new ways to work with arrays including using the spread operator and extending arrays by creating tuple types. I then demonstrated several ways you can combine existing types to make your code more flexible. We looked at union types, intersection types, and mixins. Finally I showed you how to use string literal types and type aliases which I think are both handy features to help you keep your code clean and readable. In the next module, we will keep working with types but look at more advanced features like creating custom type guards, declaration merging, and creating fluent APIs. Stay tuned.
Using Advanced Type Features
Introduction and Overview
Hi, this is Brice Wilson. In this module, I'm going to cover some much more advanced features available when working with types. They'll help you write cleaner code with fewer errors. You'll also learn about some new capabilities you may not have even known existed in JavaScript and TypeScript. I'll begin by covering a feature known as polymorphic this types and show you how the special this keyword can have a different type depending on the context in which it's used. Next, I'll show you how to implement declaration merging which is a technique for combining multiple declarations with the same name into a single declaration. After that, I'll show you how to use type guards. There are several ways to use them, but they all make your code easier to write and help the compiler find errors before your code is deployed. The last topic I'll cover in this module is symbols. They're a new feature added to ES2015 that is also now supported in TypeScript. I'll explain what they are and show you a few practical applications for them. Let's get started with polymorphic this types.
Polymorphic this Types
In this section, I'm going to cover polymorphic this types and show you how they enable you to build a fluent API in TypeScript. Let's start with the definition. A polymorphic this type represents a type that is the subtype of the containing class or interface. What this really means is that the type of the object referenced by the keyword this may be the type of an inherited class or interface. I know that it still sounds a bit abstract, so let's look at an example. Let's imagine that I have a class named Vehicle and then it has a method named Drive. It does some work, and at the end of the method, it returns the value referenced by the keyword this. This is the this that is polymorphic, meaning that it can represent more than one type. If I were to write some code like this that declares a new instance of my Vehicle class and then calls the Drive method, the type of the value returned by the method would be Vehicle. However, let's now suppose I create a class named Car that extends the Vehicle class. It's a subtype of Vehicle. It has its own method named CarryPeople but it also inherits the Drive method from the Vehicle class. If I create a new instance of it and call the Drive method, the type of the value returned in the this reference will be Car. Similarly, I could create a second class that extends Vehicle named Truck. If I call the Drive method on a Truck instance, the type returned by this will be Truck. It's polymorphic because depending on the context, it could be a Vehicle type, Car type, or Truck type. One of the things this enables is the ability for us to build hierarchical, fluent APIs. I'll show you an example of that in a demo next.
Demo: Creating a Fluent API with Polymorphic this Types
In this demo, I'm going to take advantage of polymorphic this types to create a fluent API for books in the Library Manager app. I'm here in the app.ts file and I've removed the demo code from the last course module just so we can start this module without quite so much clutter. You'll still be able to get all of the code from the last module in the downloadable course materials. I'm going to start by creating a simple new class named LibraryBook. I use a code snippet to paste in the body of the class which includes two methods, Checkout and Checkin. In a more realistic implementation, they would implement some business logic related to the process of checking in and out books from the library. This simple version just logs the action that's taking place. The more important piece of the code though is really the return type of each method. Notice that I specified this as the return type for both and then I explicitly returned this on the last line of each method. As we saw on the slides, it's the polymorphic nature of this that's going to enable us to create a fluent API using the LibraryBook class. I'll come down a couple of lines and paste in another class named ChildrensBook that extends the LibraryBook class above. Since kids often color in books and generally treat them a little rougher than adults, I've added a method of this class called Clean. Just like the methods in the parent class, this method's return type is this and I'm explicitly returning it on the last line. I'll now create a new ChildrensBook instance and show you how I can use it. A fluent interface or fluent API is an implementation that allows you to chain methods together, which hopefully produce more readable code. This works only if the method returns the object instance on which the methods exist. I accomplish that by returning this in the methods above. Let's suppose the kidbook instance I have here has just been returned to the library, but I need to immediately check it out to another customer. I would first call the Checkin method. That method returns this, which, in this case, is an instance of a ChildrensBook, so I can call the Clean method on it. We want the books to be clean before checking them out to someone else. The Clean method also returns this, so I can immediately call the Checkout method. Having each method return this allows this type of method chaining. You could chain them together on one line like this, but my personal preference is to put each method call on its own line. I, again, want to explicitly point out the polymorphism in this example. It's the LibraryBook class above that contains the Checkin method. That method returns this, which would normally be a LibraryBook instance, which does not include a Clean method. I'm only able to call Clean here because the type of this returned from Checkin will actually be a ChildrensBook since that's the type of the variable I created. The this type is polymorphic because it can be either a LibraryBook instance or a ChildrensBook instance, depending on the context in which it's used. I'll quickly run this just so you can see it works as expected. I'll first start the build test that compiles the code and then I'll press F5 to run it. You can see the expected log statements from each of three methods in the console. I'll now make it a little more interesting by pasting in another new class that extends the LibraryBook class. This one is called ElectronicBook and includes a method named RemoveFromCustomerDevice. Just like the methods in the other classes, the return type is this and I explicitly returned this at the end of the method. I'll paste in an example below that creates a new ElectronicBook instance and chains together several method calls on it. The important part here is that I'm able to call the RemoveFromCustomerDevice method on the type return from Checkin method. The type of this return from Checkin in this example will be ElectronicBook instead of LibraryBook. Before I run this, I'm going to tweak the code in the Checkin method so that it outputs the type of this during each call. I'll comment out the existing log statement and paste in a couple of conditional checks. In the first if block, I'm using the instanceof operator to see if the type of this is a ChildrensBook. If it is, I just log a message to that effect. I do the same type of check just below that to see if this is an ElectronicBook. These checks should make it very clear that the type of this is context-sensitive. I'll run the code one more time, and you can see in the output that the type of this inside the Checkin method is different for each instance. In the first example, it's a ChildrensBook, and in the second, it's an ElectronicBook. Okay, let's now go back to the slides and talk about declaration merging.
Declaration Merging
In this section, I'll explain declaration merging and show you a couple of scenarios when you may want to use it. First off, let's define this new term. Declaration merging is a process in which the compiler merges two separate declarations declared with the same name into a single definition. The compiler, basically, performs a bit of magic to make multiple declarations with the same name look as if they were a single declaration. Let's look at an example. Let's suppose I declare a new interface named Employee. The interface has a name property and a doWork method. Elsewhere in my app, I declare another interface named Employee. This one has title and phone properties. The TypeScript compiler will see these two interfaces and effectively combine them into a single Employee interface. When you use the interface in your app, it will be as if all of the members from both declarations were actually in a single declaration. I should point out that the process I've shown on this slide is merely conceptual. The TypeScript compiler actually never outputs interfaces since they don't exist in JavaScript. Declaration merging just allows you to use them as if they were a single declaration. Let's quickly run through some TypeScript constructs that can and cannot be merged. As I showed you in the last slide, you can merge interface definitions. You can also merge enums and namespaces. Namespaces can be merged with other namespaces, but they can also be merged with other types of declarations. For instance, they can be merged with classes. If you give a class and a namespace the same name, they'll be merged. The result is a nested structure that resembles inner classes that exist in other languages. Namespaces can also be merged with functions. This allows you to assign property values to a function. They can also be merged with enums, allowing you to create static members on the enum, adding a static function, for example. A notable merge that is not allowed is the merging of classes with other classes. Although you can't declare two classes with the same name and have them merged by the compiler, you can accomplish something similar using mixins as I discussed in the previous course module. In the next clip, I'll demonstrate module augmentation, which, I think, is probably the most useful type of declaration merging.
Demo: Interface Merging and Module Augmentation
In this demo, I'll first show you a simple example of merging interfaces. I'll then show you a more practical example of using declaration merging to augment a module. I'm going to start with an example of interface merging so I'm here in the interfaces.ts file. This file contains the Book interface I've been using the course. Just below that interface, I'm now going to create another one that also has the name Book. I'll give it two properties, publisher will be a string and hasIndex will be a Boolean. Because these two definitions have the same name, the TypeScript compiler will merge them into a single interface. This is easy to see when you attempt to use the interface. I'll go back to app.ts and create a new variable named mergedBook that is a Book type. If I now type a dot after the variable name, the code completion support clearly shows all of the members from the merged interface definitions. You can see that the publisher and hasIndex properties have been added to the list of members available on the original interface definition. I'll choose the publisher property and assign it the string. Let's now take a look at what I believe to be a more practical example of declaration merging. It's known as module augmentation and is a technique that allows you to extend existing modules with new members. It's a nice way to extend modules you might not maintain. I'll quickly jump over to the classes.ts file and show you the UniversityLibrarian class. It contains a few properties and a few methods, but I'm now going to build an extension to the class that will be merged with this original definition at compile time. I'm going to create the extension in a new file. I'll name the file LibrarianExtension.ts. The first thing I need to do in the new file was import the UniversityLibrarian class from the classes module. In order to augment or extend that module, I need to declare a new module with the same name. Inside that declaration, I'll create an interface named UniversityLibrarian and give it a phone property and a method named hostSeminar that takes a topic as a parameter. This interface will be merged with the UniversityLibrarian class definition and new instances will have all the members from both definitions. The last thing I need to do is provide an implementation for the hostSeminar function. I'll do that by assigning a function to the hostSeminar property of the UniversityLibrarian prototype. That's all I need to do. These new members will now be merged with the original class by the compiler. I'll go back to app.ts and create a new instance of it. I first need to jump up to the top of the file and import the contents of the new LibrarianExtension file. I'll now create a new variable named newLibrarian and assign it an instance of UniversityLibrarian. When I type a dot after the variable name, you can see that this instance includes the members from both definitions. I've got all of the original members as well as the phone and hostSeminar members. I'll assign a value to the phone property and then call the hostSeminar function. I'll run it, and I get the expected output in the console. I think this type of module augmentation is a nice way to extend third party code that you may not be responsible for maintaining. So definitely keep it in mind the next time you're using a library that is missing a function or two you wish it had. Let's now go talk about type guards.
Type Guards
In this section, I'm going to cover type guards. They're a way for you to check the type of a variable, but perhaps more importantly, they're a way for the compiler to narrow a variable to a specific type. The advantage of this is that the compiler can check for more errors based on types and you get a better development experience since TypeScript to where editors can deliver better code completion support. There are several ways to implement type guards. The first one we'll look at is typeof type guards. typeof type guards make use of the JavaScript typeof operator you may already be familiar with. Let's look at a simple example. Here, I've declared a new variable named x to be a union type. It's value may either be a string or a number. I'm initializing it to a number here. I may want to perform different actions based on which of those two specific types x actually is assigned. To do that, I can write a typeof type guard like this. I use the typeof operator in front of the variable and test whether or not it's equal to a particular type represented as a string. In this type guard, I'm checking to see if x is a string. If the result of this conditional check is true, then the type of x inside the if block will be narrowed to a string. I could then call any string method without any complaints from the compiler. If I also include an else block like this, I know that the type of x inside this block will be narrowed to a number. Since I declared x to be a string or a number and the if block checks to see if it's a string, then I and the compiler know that it must be a number in the else block. typeof type guards are nice, but they take a very specific form and may only be used with certain types. You can only use them to test if a type is equal or not equal to a specific type, and the only types you can test for are string, number, Boolean, and symbol. You're certainly familiar with those first three, and I'm going to cover symbols at the end of this module. Fortunately, there are other flavors of type guards we can use when working with other types. instanceof type guards make use of the instanceof operator and allow you to compare the type of a variable to a type that has a constructor function. In most cases, this will mean comparing the type of a variable to a class. Let's imagine I have a simple class named Phone that has one method named callSomeone. I might also have another class named Tablet that also has a single method. I could declare a new variable named device that would have a union type including Phone and Tablet. I'm initializing it here to a new Phone instance. I can write an instanceof type guard for the variable like this. It uses the instanceof operator to test that the variable on the left side of the operator is in the prototype chain of the constructor function on the right side of the operator. Since classes in TypeScript have constructors even if they're not explicitly declared, they can be used with instanceof type guards. If this if statement evaluates to true, then the compiler knows that the type of the device variable inside the block has been narrowed to a Phone. Therefore, I can call the callSomeone method as I have here. User-defined type guards give you the ability to check additional types that don't have constructor functions. They're pretty easy to implement, but you do have to write a new function to perform the check. They're great for checking interfaces so I'll add a simple Vehicle interface here with one property. To create a type guard for this interface, I'll add a new function. The function needs to have one parameter which will be the object whose type you want to check. I've named the parameter v and specified that it has the any type. You're not required to use the any type here. I could've used the union type if I was sure I would only ever call the function with a small set of possible types. The primary syntactical difference between regular functions and custom type guards is the so-called type predicate specified as the return type of the function. The predicate consists of the function's parameter name followed by the word is and the name of the type you're checking for. The body of the function should contain a check to see if the parameter is in fact the type you're checking for and return true if it is and false otherwise. I'm performing the check here by using a type assertion to force the compiler to treat the variable as a Vehicle. I then check for the existence of the numberOfWheels property by comparing it to undefined and return the result of that comparison. If the property is not undefined, then it must exist, so I return true since that is the only criterion I have for something being considered a Vehicle. Once you've written the function, it's pretty easy to use. If I create a new instance of a Car class like this, I could use the new type guard to see if it's a Vehicle with a check like this. I just call the isVehicle function and pass it the variable containing the Car object. If it returns true, then TypeScript will allow me to treat the variable as a Vehicle inside the if block. Let's now go see some additional examples of type guards in a demo.
Demo: Using typeof Type Guards
In this demo, I'll show you how to create and use typeof type guards. The first type guard I want to demonstrate is a typeof type guard. To do that, I'm going to write a very simple function named logVisitor. It takes one parameter which may be either a number or a string. I'm going to use typeof type guards to check the actual type of the parameter passed to the function and take slightly different actions based on that. The type guard I'm going to use really consists of using the typeof operator inside an if statement. I'm comparing the type of the parameter to the string number. If that test is true, then I'll print the value as part of a log statement inside the if block. Otherwise, the parameter must be a string and I'll log it in the else block with a different message. The whole point of type guards is that they allow the compiler to perform additional type checks on your code. This allows you to get better code completion support in your editor and it allows the compiler to prevent you from using types incorrectly. Notice that if I hover over the parameter name in the if statement, I can see that the type of the variable is the union of number and string. That's because that is how the type of the parameter was declared. However, after the type guard is executed, the compiler knows more about the specific type of the variable. If it's a number, the code inside this if block will execute. If I hover over the variable inside the block, you can see that the type has been narrowed in that code path to only a number. Similarly, if it drops into the else block, it must not be a number, so hovering over the variable inside that branch reports that it's been narrowed to a string since that is the only other possible type for the parameter. Since it must be a string inside the else block, my editor treats it as a string and gives me code completion support based on that. I can just type a dot after the variable name and get a list of all of the string methods I could call on it. I'll call the toUpperCase method so that it's logged in all uppercase. I'll quickly call the function with two different values so you can see the different output. I'll call it once with a number and a second time with a string. I'll run it, and you can see the type guard did it's job and we got the two different log statements in the console. typeof type guards are great when you're working with primitive types, but you can't use them with more complex types. Okay, I'll comment out these lines while we take a look at using instanceof type guards with classes in the app.
Demo: Using instanceof Type Guards
In this demo, I'll show you how to create instanceof types guards in the Library Manager app. I'm going to go over to the classes.ts file and make sure we have a couple of good example classes to work with. I'll first add a new method to the Universitylibrarian class I've already got. I'll name it assistFaculty and it'll just output a simple message. I'll then come down a little and paste in a new class named PublicLibrarian. Notice that just like the UniversityLibrarian class, the PublicLibrarian class implements the Librarian interface. In addition to the properties and methods on that interface, the class also includes an additional method specific to this class named teachCommunity. So what I now have is two classes in this module that both implement the Librarian interface but that also each have a method that is unique to the class, the assistFaculty method on UniversityLibrarian and the teachCommunity method on PublicLibrarian. I'll now go back to app.ts so we can experiment with type guards for these classes. The first thing I'll do is jump up to the top and import the PublicLibrarian class from the classes module. I'll then come back down to the bottom and write some code. I'll declare a new variable that can store any object that implements the Librarian interface. I'll now paste in two instanceof type guards that will check the specific type of the object in the lib variable. instanceof type guards test the type against the constructor function. You place the instance you want to test on the left of the instanceof operator and the constructor function you want to compare it to on the right of the operator. If the instance has the constructor function in its prototype chain, then the test returns true. TypeScript treats this as a type guard and will then narrow the type to the specific type you tested for. Notice that if I hover over the lib variable inside the if statement, it has the same Librarian type I gave it when I created the variable. However, inside the if block, TypeScript has narrowed the type to UniversityLibrarian. That allows me to call methods on the variable that only exist on UniversityLibrarian instances like the assistFaculty method. The same thing is happening in the second type guard. I'm testing the lib instance to see if the PublicLibrarian constructor is in its prototype chain. If it is, then the type of the variable will be narrowed to a PublicLibrarian inside the if block. That allows me to call the teachCommunity method which only exists on PublicLibrarian instances. Before I run this code, I need to assign an actual instance of one of the classes to the variable. I'll assign it a new PublicLibrarian instance. If I now run it, you can see that the type guard narrow the type of the variable from Librarian to PublicLibrarian and then the teachCommunity method was called inside the if block. Okay, let's now see how to create a custom type guard.
Demo: Creating and Using Custom Type Guards
In this demo, I'll show you how to create your own custom type guards. I'll first comment out all of the previous code just so we're not distracted by it in the output. Custom type guards are handy when you need to test types that you simply can't test with typeof or instanceof type guards. Interfaces are a good example of types you can't test with either of those techniques. They're not primitive types you can test with a typeof type guard and they don't have constructor functions you can test with an instanceof type guard. The custom type guard I'm going to write will test that the object passed to it is a Book, which, you may recall, is defined as an interface. I'll name the type guard function isBook. It will accept one parameter which will be the instance to be tested. I'll specify that the function should only accept types that are either a Book or a Magazine. The syntax for the function return value is what makes this a custom type guard. For this function, it will be text is Book. text is the name of the function parameter and Book is the type I'm testing for. Inside the function, I need to return a Boolean. True, if I determine the parameter is a Book, and false otherwise. I'm going to keep this test simple. I'll use a type assertion so that the parameter is treated as a Book, and then I'll check for the existence of an author property on it. If the author property is not undefined, then it must be a Book. I'll now write a bit of code to use it. I'll declare a new variable named readingMaterial that can contain a Book or a Magazine. I'll paste in some code that uses the new type guard. The if statement calls the isBook function and passes it the readingMaterial variable. If it returns true, then TypeScript will narrow the type of the variable inside the if block to a Book. If it returns false, then the type will be narrowed to a Magazine inside the else block since that's the only other type the variable can be. You can see evidence of this if I hover over the variable in each code path. In the if statement, it's still defined as the union of Book and Magazine. Inside the if block, it's defined as a Book, and inside the else block, it's defined as a Magazine. This also allows me to use properties specific to those types inside those blocks. So that I can quickly run this code, I'm going to call the GetAllBooks function and assign the first element of the array it returns to the readingmaterial variable. I'll run the code, and you can see that the type guard did correctly determine that the variable contained a book and logged the correct message to the console.
Symbols
The last topic I'm going to cover in this module is symbols. This is another new ES2015 feature that TypeScript adopted. Symbols are a very new feature in JavaScript and TypeScript, so let's go over a few of their characteristics. They were added to JavaScript in the ES2015 version of the language and quickly supported in TypeScript as well. There are new primitive data type, and the primary features that set them apart from other primitives are that symbols are unique and immutable. Every symbol you create is different from every other symbol, and once you've created them you can't change them. I'm sure all of this sounds a bit abstract at this point, so it would be very reasonable to ask why. Why does the language need a new primitive type? Why should you bother learning about it? And why would you ever use it? Those are good questions. I'm not going to pretend that this is some major new language feature that you'll immediately begin using every day, but it does have a few specific use cases. Being unique and immutable means symbols make good unique constants. If you are writing ES2015 JavaScript code, you could use them to implement objects with enum-like behavior. This isn't that significant for TypeScript developers because TypeScript supports enums natively. Symbols can also be used as computed property declarations. Computed property names can also be strings, but the uniqueness of symbols means you won't have to deal with name collisions. I'll show you an example of using a symbol as a computed property in the next demo. There's also a shortlist of so-called well-known symbols that can be used to customize internal language behavior. There are built-in values you can use to override certain familiar behaviors. In the next demo, I'll show you how to use a symbol to customize how the instanceof operator works.
Demo: Experimenting with Symbols
In this demo, I'll show you how to create symbols and use them as computed properties and as a mechanism for customizing how the instanceof operator works. Before I start writing any code that uses symbols, I need to change the output target being used by the TypeScript compiler. Symbols are a new feature in ES2015, so I can't use them in TypeScript that I'm attempting to compile down to ES5. I defined my compiler options in a tsconfig.json file inside my project. Inside the file, you can see that the target is currently set to ES5. For this demo, I'm going to change that to ES2015. Keep in mind that if you compile your code to ES2015, it may not all work in every browser. The browser vendors are rapidly adapting ES2015 features, but as I record this, they all still have some gaps in their implementations. Okay, I'll now go back over to app.ts. I'll create a new symbol and assign it to a variable. You create new symbols by calling the symbol function. If I pause here for just a second, you can see the code completion support I'm getting for that function. Notice that you can optionally pass it a description that can either be a string or a number. The description doesn't affect how the symbol is created and is only there for debugging purposes. I'll create my first new symbol with the description first_symbol. One important bit of syntax I want to point out here is that I simply called the symbol function. I did not treat it as a constructor and used the new keyword with it. In fact, as you can see, you'll get an error if you try to do that. Just to show you that the description passed to the function doesn't affect or determine the symbol that's created, I'm going to create another one with the same description. I'll now log whether or not the two symbols are the same. I also want to make the point that symbols are a new primitive data type. I'll do that by using the typeof operator and logging the type of one of my new symbols. I'll run the app, and you could see in the console that the two symbols are not equal even though they have the same description. You can also see that the type of a symbol isn't string, number, or object as you might expect, but it's the new primitive type symbol. With those basics out of the way, let's now look at a couple of practical applications of symbols. Because they're constants, symbols make good property keys. You may have used strings for this in the past, but if your target environment supports symbols, I would recommend beginning to use them instead of strings. To quickly demonstrate how this works, I'll create a new object literal and use one of the symbols I created earlier as a property key on the object. I just surround the variable containing the symbol with square brackets and then assign it a value like you would with any other property on an object literal. I can then retrieve the value by adding the symbol again inside square brackets after the name of the object containing the property. I'll run this, and it correctly logs the value I stored in the object. I now want to show you how you can use a symbol as a computed property key. I'll open the classes.ts file. I'm going to create and export a new symbol from the module. I'll call it CLASS_INFO and make it all uppercase to remind me that it's a constant. I'll now come down inside the UniversityLibrarian class and create a new computed property using a symbol. I'm going to make it a method that just logs some basic information about the class. I put the symbol name inside square brackets in place of a method name, but it otherwise looks like any other method on the class. It has parentheses to indicate it's a method as well as a return type. Obviously, I didn't have to use a symbol to write a method like this, but I want to show you how to do it. Implementing a technique like this guarantees that the property or method you create with the symbol will be unique. And that can be important if you're concerned with avoiding naming collisions. Let's now go back to app.ts and see how to call this method. The first thing I need to do in this file is import the CLASS_INFO constant containing the symbol. I'll now go back to the end of the file and create a new UniversityLibrarian instance. In order to call the method defined with the symbol, you access the method using the symbol as a key. The symbol is placed in square brackets. Since the class member, in this case, is a method, I add a pair of parentheses at the end to execute that method. I'll quickly run it, and you can see that the method did execute and output the correct message. Okay, the last symbol example I want to show you is how you can use symbols to customize certain internal language behaviors. I've already shown you in this module how the instanceof operator can be used as a type guard when it checks to see if a specified constructor function is in the prototype chain for an object. It turns out that the instanceof operator is represented by one of the so-called well-known symbols. Well-known symbols are built-in symbols that represent a handful of internal language behaviors. I'm going to go back to the UniversityLibrarian class and use one of the well-known symbols to effectively override the way the class responds to the instanceof operator. To do this, I need to create a new static method in the class and use the well-known symbol as the method name. The well-known symbol used for the instanceof operator is named hasInstance and it's referenced as a property on the symbol object. I create that name in square brackets just like I did the CLASS_INFO method above. The method will be passed an object which will be the object being tested to see if it's an instance of a UniversityLibrarian. The method needs to return a Boolean. It should return true if the object passed in is an instance, and false otherwise. You can perform whatever test you need to determine that inside the body of the method. In this example, I'm going to see if the object has a name property and an assistCustomer method. If so, the method will return true, which means the instanceof operator will return true. Let's now go see this in action. I'll go back to the app.ts file. I'll create a new object literal named libraryCustomer and just give it a name property. I'll now use the instanceof operator to see if that object is an instance of UniversityLibrarian. If so, I'll log a message to that effect. Otherwise, I'll log that the object does not contain a librarian. Before I can run this code with Node, I need to make sure I pass it an additional command line argument. I'm using Visual Studio Code, and its executing Node for me based on the configuration in this filename launch.json. If you're not using VS Code, you can use the same argument I'm about to show you when you run it from the command line. I'm executing all of this code with Node version 6.2.2. In order to get that version of Node to recognize my use of the hasInstance symbol, I have to pass it the --harmony-instanceof argument. With that last bit in place, I'm ready to run this code. You can see that the result is that the object is not a librarian. That's because my test said it had to have a name property and an assistCustomer property. The object I'm using here only has a name property. I'll now change that and use an arrow function to give it a very simple implementation of a method named assistCustomer. I'll run it one more time, and this time, you can see the object was found to be a university librarian. Using symbols to override functionality like that is admittedly a feature you may only rarely have an occasion to use. But you now have that tool in your tool belt and can pull it out when you need it.
Summary
In this module, we looked at some of the more advanced ways we can work with types in TypeScript. I first explained polymorphic this types and how you can use them to create clean, readable, fluent APIs. I then explained the idea behind declaration merging and showed you how the technique can be used to augment modules you may not maintain. Next, we looked at type guards. They're one of my favorite features. I really like how they narrow the type of a variable inside a block. This allows of better code completion support and compile time type checking which means fewer errors when your code gets deployed. I wrapped up with a look at symbols and I showed you a few practical ways to use this new ES2015 feature that's now supported in TypeScript. You likely won't have a need to use many of these advanced type features on a daily basis, but before you can ever solve a problem with one of them, you have to know they exist as a possible solution. You now know what's possible and will be ready to implement them when the time comes. In the next module, I'm going to focus on decorators and how you can use them to add functionality to lots of different TypeScript constructs.
Creating and Using Decorators
Introduction and Overview
Hi, this is Brice Wilson. In this module, I'm going to cover decorators. They're chunks of functionality you can add to classes, methods and other constructs with a very simple syntax and they play an important role in several large client side frameworks like Angular2. Lots of languages have a similar feature so if you've ever used annotations in Java or attributes in C#, then you'll feel right at home with decorators in TypeScript. I'll start this module off by first answering a couple of questions. What are decorators and how are they implemented? I just gave you a little preview of what they are but I'll give you some more background and go over some use cases as well. I'll also cover the syntax required to both define your own decorators as well as the syntax you'll use when applying them to declarations throughout your code. There are several different types of decorators and I'll touch on all of them and point out some of the slight differences in how they're implemented.
What Are Decorators?
As usual, I think it's best to start with a definition or probably more of a description in this case. What are decorators? For starters, decorators are proposed feature for a future version of JavaScript. This is a perfect example of how TypeScript let's you live in the future and take advantage of a feature today that may not be available in browsers for several months or even years. TypeScript decorators compile down to ES5 just fine so you can use them today and know they'll work fine in all the major browsers. They're a form of declarative programming which means once you create a decorator, you can apply it to classes, methods and other things effectively describing what those things should do rather than how they should do them. Decorators are simply implemented as functions. Those functions can have different signatures depending on what construct they'll decorate, but they're still just regular TypeScript functions. They can be attached to classes, methods, accessors, properties, and parameters. Because decorators are still just a proposal for a future version of JavaScript, they're considered an experimental feature in TypeScript. In order to use them, you have to use the experimentalDecorators compiler option. Let's now take a look at the syntax for creating and using decorators.
Decorator Syntax and Factory Functions
Decorators are implemented as TypeScript functions. Here I've got the shell of a function I can use as a TypeScript decorator. I've named it uielement and it will be a class decorator. Class decorators take a single parameter that is the constructor function for the class. I've named the parameter target and given it the type Function. Below this, I'll add another one that I'll use as a method decorator. I've named it deprecated and it would just log a message warning developers that the method it decorates may be removed soon. The functions used as method decorators have a different signature than those used for class decorators. As you can see, this one takes three parameters. I've used very unhelpful parameter names just to save a little space on the slide. In any case, the first parameter is either the constructor function for a static method or the prototype for the class if it decorates an instance member. The second parameter is the name of the decorated member. And the third is the property descriptor for the member. Actually applying these functions as decorators is pretty simple. You just use the at symbol followed by the function name just before the declaration of the item you want to decorate. Here I've got a class named ContactForm and I'm applying the uielement decorator to it. Think of it as a stylish hat for your class in this case, decorative but still quite functional. The class' constructor function will automatically be passed as the parameter to the uielement function. So there's no need for me to specify any parameters as part of the decorator. I can apply the deprecated decorator to a method in the class like this. I just used the at symbol again followed by the name of the function. Just like the class decorator, all of the parameters for the underlying function will be passed in automatically by TypeScript. Let's now talk about decorator factories. They give you the option of specifying additional parameters when the decorator is applied. I'm going to re-implement the uielement decorator from the previous slide as a decorator factory. It's similar to the decorator functions we've already seen. You define a function and give it the name you want to use for your decorator. However in this case, the function can accept a set of parameters that will be specified when the decorator is applied to different constructs. In this example, the uielement decorator will accept a single string parameter named element. Inside this factory function, we need to return a function that has the signature of the specific decorator type it will be used with. This will be a class decorator so the factory function needs to return a function that accepts one parameter that will be the constructor function for the class. The benefit of this approach is that you can use the parameter specified when the decorator is applied inside the actual decorator function returned from the factory. Here I'm logging the parameter named element that was passed to the factory function. I can use this by applying the decorator to a class and passing a string to it much like you would any other function call. In this example, the string SimpleContactForm will be output as part of the console.log statement above.
Class Decorators
Class decorators are likely the most common type of decorator you will encounter and use. If you were to dig around in the TypeScript source code, you would find that the type definition for our class decorator looks like this. It starts with a type parameter represented here by TFunction, that will hold the type of the class' constructor function. The constructor function will be passed as a parameter to the decorator. You can see here that it uses the TFunction type parameter for that. There are two ways you can use class decorators and that's really represented by the two possible return types in the function signature here. The first option is to replace the existing constructor function. The TFunction type parameter represents the type of the constructor passed to the decorator and it also represents one possible type returned from the decorator. If the decorator function returns a value, it must match the signature of the class' constructor function since that returned value will then become the new constructor for the class. However, class decorators aren't required to replace the constructor. If the function returns void, then the original constructor will remain in place and the logic inside the function will execute like any other function. In the next two demos, I'll show you an example of each type of class decorator.
Demo: Creating and Using Class Decorators
In this demo, I'll show you how to create and use class decorators in TypeScript. Before I can start using decorators in my app, I need to enable a special TypeScript compiler option. I've configured the TypeScript compiler for this project in a tsconfig.json file. I've got it open here. All I need to do is go to the end of the compiler option section and include the experimentalDecorators option and set its value to true. With that in place, I'm going to create a new file named decorators.ts where I'll keep the functions that define my decorators. The first decorator I'm going to create will be a class decorator named sealed. I'll apply it to classes when I want to prevent new properties from being added to it. Before I write the body of the function, I want to explicitly point out the signature I'm using. You can tell it will be a class decorator because it takes a single parameter that's a function object. Class decorators are passed to class' constructor function as a parameter. Also notice that I've declared the function return type to be void. What this means for class decorators is that the class' constructor function will not be replaced by the decorator. I'll show you another example shortly that does replace the constructor. I'll use a code snippet to paste in the body of the function. I log that the constructor is being sealed so we can see evidence in the console that it's working. I then use the built-in seal function on Object to seal the constructor that was passed in as well as its prototype. I'm now ready to apply it to classes in the app. I'll open classes.ts. Before I can use it in this module, I need to import the function from the decorators module I just created. I can now scroll down a bit and apply it to the UniversityLibrarian class. It sits at the top of the class like a hat. I just type the at symbol followed by the name of the function. Since I didn't implement the decorator as a factory with custom parameters, there's nothing else I need to add here. The class constructor parameter expected by the sealed function will automatically be passed by TypeScript. To see this in action, I'll go over to app.ts and create a new UniversityLibrarian instance. I removed the code from the previous course module just to keep it cleaner, but it's all available in the download materials. All I'm going to do right now is create a new variable named lib1 and assign it a new UniversityLibrarian instance. I'll start to build task that compiles the code and then press F5 to run it. You can see the log statement from the decorator in the console so I know I applied it correctly. However, I'd like for the log message to include something specific to the class that was decorated. To do that, I need to convert my decorator function to a decorator factory and pass in a new parameter. I'll go back to decorators.ts and make that change. The decorator will still be named sealed but I wanted to take a parameter named name. I'll now copy the original function and paste it inside this new function. The outer function is the factory function and it needs to return the inner function. I'll first fix the indentation and then rather than export the inner function, I'm going to return it. I also don't need the function name on the inner function so I'll remove that. I now want to use my name parameter in the return function that actually becomes the decorator. I'll just add it to the end of the console.log statement. Notice that the signature of the inner function hasn't change. It will still automatically be passed the constructor function for the decorated class. However, since this is now a factory decorator, I'm responsible for passing the parameter expected by the factory function. I do that when I apply the decorator to a class. I'll go back to classes.ts and make that change. You can see that I now have the red squiggly under the decorator letting me know something is not quite right. That's because it's now expecting to be passed a string. I do that just like I would pass a parameter to any other function. I'll add a pair of parentheses and a string literal in this case. I'm going to pass it the class name being decorated. I could do the same thing when I apply this decorator to other classes or use whatever string I want. I'll run the app again and this time you can see the additional string was added to the log statement in the console. In the next clip, I'll show you how to create a decorator that replaces the class' constructor function.
Demo: Class Decorators That Replace Constructors
In this demo, I'm going to create another class decorator but this time I'll show you how to return a new constructor function for the decorated class. I'm going to create the new decorator in the decorators.ts file. I'm going to name it logger. Since it will return a new constructor, I'm going to give the function a generic type parameter that represents the type of that constructor. I'm going to name it TFunction and it will extend the Function type. Remember that in the first decorator I created above, the class constructor was automatically passed to the decorator and its type was Function. The type parameter on the logger function is an extension of that same type and will be used in the function to represent the type of the replacement constructor. The first place I'm going to use the TFunction type is for the parameter passed to the decorator. This signature will still work for the constructor automatically passed to the decorator since TFunction is an extension of Function. Since I intend to replace the constructor function, I'll declare that this function will return a TFunction. This is another difference from the sealed decorator above. I didn't replace the constructor in it so its return value was just void. I'll use a code snippet in the body to declare a function object that will become the new constructor. Since this is just a simple logger decorator, I'll have it log that a new instance is being created and then I'll log the parameter that was passed in. The effect of that is just that we'll get to see the name of the class in the console. Since this new function object will become the new constructor, I need to assign it the prototype of the constructor passed to the decorator. I'll do that by creating a new object and assigning it the prototype of the new function. I'll then assign the parameter itself to the constructor property of that prototype. The last thing I need to do is return the newConstructor function. I'll use a type assertion to effectively cast the function object to the TFunction object required for class decorators. The last thing I want to point out here is why I had to explicitly declare the newConstructor variable to have the type Function. If I remove that type annotation and allow TypeScript to infer the type, you can see that the type changes. It's no longer a function object, it's a very specific function type. It's a function that takes no parameters and returns void. That generates an error below when I try to return that variable as a TFunction. I'll hover over it, and the message lets us know that the function type and TFunction are not assignable to each other. I'll put the type annotation back on the declaration. I'm allowed to cast the function object to TFunction because my generic parameter declared that TFunction is an extension to Function. Let's go write some code to use this new decorator. I'll go back to classes.ts. I first need to add the logger function to the list of imports. I'll then add the decorator to the UniversityLibrarian class. I'll also come down a little further and add it to the PublicLibrarian class. I'll go over app.ts and create a new PublicLibrarian instance just below the UniversityLibrarian instance so we can see how the decorator applies to different classes. I'll run it and you can see a pair of log statements in the console for each class. Since I logged the constructor parameter, the first set of statement shows the UniversityLibrarian class and the second shows the PublicLibrarian class. Let's now go talk about the other types of decorators you can create with TypeScript.
Property and Parameter Decorators
I'm going to go through these additional decorator types pretty quickly since the way you create and use them is very similar to the class decorators I've already shown you. I don't want to bore you with too much repetitive information so I'll focus on the small differences between them. Here, I have an example of a function I could use as a property decorator. The only real difference between a function used as a property decorator and one used as a class decorator is the function signature. This property decorator function takes two parameters. The first will be the constructor function of the class that contains the decorated member if the member is defined as static. Otherwise, it would be the prototype for the class containing the member. The second parameter is just a string that specifies the name of the decorated property. You apply decorators to a property the same way you apply them to a class. You just use the at symbol, followed by the name of the function that defines the decorator just before the member you want to decorate. Parameter decorators are nearly identical to property decorators. The first two parameters to the decorator function are the same. The first one is either a constructor function or the class prototype and the second one is that name of the function the parameter belongs to. Parameter decorator functions do have a third parameter that's different. It's the index of the decorated parameter in the list of all parameters for that function. The first parameter will have an index of zero, the second will be one, and so forth.
Property Descriptors and Method Decorators
Before I show you method and accessor decorators, I want to briefly mention property descriptors since the functions that define those decorators accept a property descriptor as a parameter. Property descriptors are a JavaScript feature that was added to the language in ES5. TypeScript includes the interface you see here to make them easy to work with. They're really just objects that describe a property and how it can be manipulated. I'm not going to cover them in great detail here but I want to point out a couple of the properties. The value property contains the value assigned to the property represented by the property descriptor. When the member represented by the descriptor is a function, the value property will contain the function definition. The writable property is a Boolean that specifies whether or not the value is read-only. I'll use these two property descriptor properties in a demo shortly. Let's now look at the function signature used for method and accessor decorators. The first two parameters are the same as those used with property and parameter decorators. The first one is either a constructor function or the class prototype and the second is the name of the method or accessor being decorated. It's the third parameter that makes this signature a little different. It's the property descriptor for the decorated method. You can use it to control how the decorated member works. One thing I didn't include in the function signature just because of space limitations is that you can choose to return a new property descriptor. That's not required, but if you do, the return descriptor will become the new descriptor for the decorated method. I'll show you an example of using method decorators with property descriptors in the next demo.
Demo: Creating and Using Method Decorators
In this demo, I'll show you how to create and use a method decorator and how to use its property descriptor to control how the method behaves. I'm in the decorators.ts file I created earlier in this module. I'm going to add my new method decorator at the end of the file. I'm going to name is readonly and use it to decorate methods I want to make sure don't get changed programmatically after they're declared. The signature for method decorators is pretty long so I'm going to put each of the function parameters on a different line just to make it a little easier for you to read. Because I'm going to use this function as a method decorator, it takes three parameters. I've named the first one target, and it will be the constructor function for the class if the decorated method is static. Otherwise, it will be the prototype for the class. The second parameter will be the name of the decorated method, and the last parameter will be the property descriptor for the method. I'm going to use the property descriptor to make the method read-only. The first thing I'm going to do in the body of the method is just log the decorated method name and specify that it's being set to read-only. I'll then just set the writable property of the descriptor to false to prevent it from being changed later. I'm now ready to apply it to a method on one of my classes. I'll jump over to the classes.ts file. The first thing I need to do here is import the new function from the decorators module. Just to keep the console a little cleaner during this demo, I'm going to comment out the class decorators I applied to the UniversityLibrarian and PublicLibrarian classes in the last demo. I'm now going to apply the new readonly decorator to the assistFaculty method on the UniversityLibrarian class above. Because it's not a factory decorator with custom parameters, I don't need to add anything beyond the at symbol and the name of the decorator function. All of the parameters the function needs will automatically be passed in by TypeScript. Before I run this code, I'll quickly go back over to app.ts. You can see that I still have code that creates a new UniversityLibrarian instance. That's sufficient for us to see that the new readonly decorator gets applied to a method on the class. I don't actually have to call the decorated method. I'll press F5 to run the code. As expected, I get a message in the console letting me know that the assistFaculty method has been set to read-only. That all works fine but I think it would be nice if I made that decorator a little more general purpose so I can use it to explicitly make a method either read-only or writable. To do that, I'm going to rewrite it as a factory decorator. I'll go back to the decorators.ts file. I'm going to name the new decorator factory writable and have it take a single Boolean parameter that specifies if the decorated method should be writable or not. The implementation will be similar to the readonly decorator above so I'm going to cut it and paste it inside the new function. I need to clean it up a little bit. I'll fix the indentation and instead of exporting the inner function, I want to return it. I'll also remove the inner function name. Since this decorator is more general purpose and won't always make a method read-only, I'm going to remove the part of the log statement that reports that's what's happening. The last change I need to make to the inner function is to use the parameter passed to the factory function to set whether or not the decorated method is writable. I'll set the writable property of the property descriptor to the isWritable parameter. I'm now ready to use it. I'll go back to classes.ts. I need to go up to the top of the file and fix the import statement. I no longer have a function named readonly, so I'll change that to be the new writable function. I'll now come down to the assistFaculty method and change it to be decorated with the writable decorator. I'm going to pass true as the parameter to explicitly declare that the method may be changed. I'll come down just a little on this file and apply the decorator again to the teachCommunity method on the PublicLibrarian class. This time, I'll pass false as the parameter which should make the method read-only. I'll go back over to app.ts and write a little code to show you the effect this decorator has on those methods. If I try to change a method that's read-only an exception is going to be thrown. So I'm going to wrap this code inside a try-catch block. I'll use a code snippet to paste in a couple of lines that attempt to redefine the two methods I decorated. I'm just attempting to assign very simple error functions to the instance methods on the lib1 and lib2 instances I created above. In the catch block, I'll just log the error message that gets generated. Below all of these, I'll attempt to call both methods. Based on what they output, we'll know for sure if they were redefined or not. I'll now run the code and have a look at the output. The first two statements in the console are from the decorator functions. The next line is actually the error message that was generated when I tried to redefine the teachCommunity method. Remember that I passed false to the writable decorator on that method. This message tells me that I cannot assign to the read-only property teachCommunity. The last two lines in the console are the output that was generated when I called both methods. You can see that calling the assistFaculty method generated the replacement message I used in the redefine method but the call to teachCommunity still output the message in its original definition since it was not actually redefined.
Summary
I really like decorators and how they make possible a declarative programming style that's previously not been available to node and client side web developments. I hope decorators are ultimately included in the future version of JavaScript but the beauty of TypeScript is that we don't have to wait for that to happen. TypeScript lets us use them now. In this module, I covered everything you need to know to get started creating and using decorators in your apps. I covered the syntax for applying decorators to declarations and also presented all the different types of decorators. The underlying functions that define decorators varies slightly and I also showed you the small differences in those signatures. In the next module, I'm going to cover asynchronous programming in TypeScript. It's one of the my favorite topics and the techniques I'll show you will definitely help you build more responsive apps, so stay tuned for that.
Implementing Asynchronous Patterns
Introduction and Overview
Hi, this is Brice Wilson, welcome back. In this module I'm going to show you how to implement several asynchronous programming patterns in TypeScript that will help you build more responsive applications that ultimately deliver a better experience for your users. I'll apply each of the patterns to the same example in the demos which I hope will highlight the differences in each technique. I'll start this module off by briefly explaining why writing and correctly managing asynchronous code is important. I'll then cover three techniques for writing asynchronous code in TypeScript. The first one we'll look at is callbacks. Support for callback functions has been baked into JavaScript and TypeScript for many years. I'll show you how they work and demonstrate some best practices for defining them. Next we'll look at Promises, they were added to the ES2015 version of JavaScript and are also supported in TypeScript. Finally, I'll show you how to use the async and await keywords to write asynchronous code that builds on top of Promises and is arguably much easier to read and write. Let's start with a look at why writing asynchronous code even matters.
Why Asynchronous Code Matters
Writing asynchronous code in TypeScript is all about building more responsive applications that deliver a great experience for your users. As an example of this, let's first look at the problem with synchronous execution. All of your TypeScript code will ultimately be turned into JavaScript code and JavaScript is single-threaded. Your single thread can just keep running and doing lots of cool work and respond to lots of different user input. However, as soon as some slow task comes along, you've got a problem. This could be something like a call to get some data from a server or reading a file from disk. While you're waiting on that network call to return for instance, you're blocking anything else from happening in your application. It may even appear to your users that the app is frozen or locked up. As soon as the long running task completes, everything returns to normal and you can resume doing cool work again. This is obviously not ideal. Let's now look at the asynchronous execution scenario. It starts out the same way with your code running fast and quickly responding to all user events. But eventually you'll need to make that same slow network call or read another huge file from disk. If you perform that slow task asynchronously, your single thread of execution is still free to continue doing cool work while you wait on the results of the slow task. Your app can continue responding to user input and your users won't get the impression that the app is frozen. As soon as the long task completes, the results can be processed and presented. This is obviously the better scenario, you're doing more work in the same amount of time and you're providing a much nicer experience for your users. There are several ways to manage this type of asynchronous code in TypeScript. The first one we'll look at is callback functions.
Callback Functions
Callbacks are made possible in JavaScript and TypeScript because those languages support so-called higher order functions. Those are functions that can be passed another function as a parameter. Callback functions are generally passed as parameters to the function that will actually perform the asynchronous work. The callback will be called after the asynchronous work is complete and will commonly process the results of the asynchronous call. Callback functions are really just ordinary functions that can have any function signature you choose to give them. However, there is a convention, particularly in the Node community to have callback functions accept two parameters. The first is an error object and the second is any data that the callback needs to process. I'll now go write some asynchronous code in the library manager app that uses a callback function to process the results.
Demo: Using Callbacks with Asynchronous Code
In this demo I'll show you how to use callbacks to process any data or errors that are returned at the completion of an asynchronous operation. I've already got the app.ts file open. I've cleaned it up and just have a few import statements at the top. I'm going to start by writing the shell of a function that will perform an asynchronous search for books by book category. I'll name it getBooksByCategory and it will take two parameters. The first will be the category I want to search for and the second will be a callback function that the function will call when the search is complete. I'm going to specify the type of the callback as a function that takes two parameters. The first is an error parameter and the second is a string array that will contain the found books. The callback function's return type will be void. The getBooksByCategory function will also return void. As you can see, this makes for a very long and hard to read parameter list. I'm going to clean this up a little bit by taking advantage of the TypeScript feature that lets me use an interface to declare function types. If you need a refresher on this you can take a look at my earlier Pluralsight course titled TypeScript In-depth. I'm going to declare a new interface named Library Manager Callback with some abbreviations which will just declare the function type I want to use for the callback I'll pass as a parameter to getBooksByCategory. I'll copy and paste the parameters I already typed below and paste them into the interface. I'll then add the void return type. With that in place I can now simplify the function declaration below by specifying that the callback will be of type Library Manager Callback. I think that's much easier to read and I can also reuse that function type in other places if I need to. Okay, I'll now work on writing the body of this function. Since I don't have a server or a database in the demo app I can use to search for books, I'm going to simulate that by making a call to the JavaScript setTimeout function. I'll pass it an arrow function that will do the searching and I'll have that searching begin after a delay of two seconds. Hopefully a search like this wouldn't really take two seconds but I want to make it easy for us to see the order in which code is getting executed when I run it. Inside the function I passed a setTimeout, I'm going to paste in a try catch block. I'll then use a code snippet to paste in the actual work to be performed. The first thing it's going to do is call the GetBookTitlesByCategory function I've defined in my utility module. We're going to pretend in this demo that's a server-side function that might take a while to return. I'm passing it the same category that was passed into getBooksByCategory. I'm storing the array of book titles that are returned as strings in a new variable named foundBooks. I then check the length of that array to see if I actually got any results. If I did, I call the callback function I passed in with null for the error parameter and the array of book titles as the second parameter. If no books were found I throw a new exception which will then be handled in the catch block below. Inside the catch block I'll also call the callback function but in this case I'll pass the error as the first parameter and null as the second parameter since there are no titles to return in this case. Once again, I want to point out at a high level what this code is doing. I've got some asynchronous work I want to perform so I pass to this function the parameter it needs to perform that work along with a function that will be called to process the results of that work. I then call that function after the asynchronous work is complete. Before I can call this new function, I need to declare another function that I'll use as the actual callback it expects as its second parameter. This function must have the same signature I declared in the interface above. I'll call it logCategorySearch and it will take an error object as its first parameter and an array of strings as its second parameter. It will return void. I'll use a code snippet to paste in the body of the function. It first checks to see if the error parameter has a value, if so, it's logged to the console. Otherwise, the array of book titles that were found are logged. I'm now ready to call the getBooksByCategory function. I'm going to surround it with a couple of console.log statements to make it obvious that the code is executing asynchronously and that the single thread of execution is not blocked by the asynchronous call. I'll add a log statement to announce that the search is beginning. I'll then call getBooksByCategory. I'll search for fiction books and I'll pass the logCategorySearch function as the callback expected for the second parameter. After that I'll add another log statement that reports that the search has been submitted. Given that the code inside getBooksByCategory is being delayed by two seconds, we should see this second log statement before the results of the search. That will be our proof that we're not blocking and creating a bad experience for our users. Okay, I'll start my build task and then press F5 to run the code. I immediately see in the console the surrounding log statements. Once the search completes, the results also get logged. It worked just as expected, we didn't block JavaScript's single thread which allowed the second log statement to be executed while the search was being performed asynchronously. I'm going to go back to the code and change it so we can quickly test the error case. I don't currently have any biography books in the library so I'll change the search category to be biography so we can see what happens when no books are found. I'll run it one more time and after a couple of seconds we see the error message logged to the console. I got this message because the callback function was called inside getBooksByCategory with an error object and a null second parameter. As you can see, using callback functions works great but there are newer techniques that let you accomplish the same thing with much cleaner code. I'll show you how to do that with Promises next.
What Are Promises?
Let's start off our look at Promises with a definition from the Mozilla website. The Promise object is used for asynchronous computations. A Promise represents a value which may be available now, or in the future, or never. They're objects that in most cases encapsulate the data returned from an asynchronous operation. You can use them to write much cleaner code to accomplish many of the same tasks you might have previously performed with callbacks. Promises were added as a native feature to the ES2015 version of JavaScript. In order to use them in TypeScript, you need to set the compiler's target option to ES2015. Promises have a very simple API. The two primary methods you'll use are the then and the catch instance methods. If you have a .NET background then it may help to know that Promises are very similar to task objects in C#. The instance methods on Promises are designed to also return Promises which means you can chain Promises together. This can be helpful when you want one asynchronous operation to begin only after a previous one has completed. The last thing I'll mention before showing you some code is that Promises are created by passing a function with a very specific signature to the Promise constructor. I'll show you how that works on the next slide.
Promise Syntax
The Promise constructor takes a single function as a parameter and that function accepts two parameters. Here I've got the shell of a function named doAsyncWork that I could pass to the Promise constructor. The purpose of the function is to perform some asynchronous work and report the success or failure of that work using the two parameters passed to the function. Those parameters are named resolve and reject, and are functions the Promise object understands. If the result of the asynchronous work is successful, you report that by calling the resolve function and passing it any data you want to make available to the code that will process the results. Similarly, if something goes wrong, you call the reject function and pass it the reason for the failure. This is a very abbreviated example, I'll show you a more complete example in the next demo. In order to use this function with a Promise, I just pass it as a parameter to the Promise constructor. Here I'm just creating a new Promise and assigning it to a variable named p. The other thing I want to point out about this declaration is the type parameter I'm using when I declare the type of the variable. The variable will be a Promise and the type parameter, a string in this case, is the type of data that will be returned if the Promise is resolved successfully. In this short example, I created a separate function declaration for the doAsyncWork function I'm passing to the Promise constructor. I did that for the sake of clarity, I think what you'll see more often and probably choose to do yourself is to pass an arrow function to the Promise constructor like this. This accomplishes the same thing as the example above. The function I'm passing in has two parameters named resolve and reject, and inside the function I call those functions as needed depending on the results of the asynchronous work that was performed. Let's now see how to process the results once the Promise has either been resolved or rejected. Handling the results returned via Promises is primarily done with two instance methods named then and catch. In this example I'm calling a method that returns a Promise. If the Promise resolves successfully, it will return a string. I've specified string as the type parameter on the variable declaration. I can now use that Promise object to register the functions that should be called when the asynchronous code succeeds or fails. I call the then function to specify the function that should be called when the Promise is resolved successfully. I'm passing it an arrow function here that accepts one parameter. The string data parameter will automatically receive the data passed to the resolve method we saw on the previous slide. Here I'm just logging the string that gets passed in. The then function also returns a Promise, so to handle errors I can just chain it to a call to the catch function. I'm also passing it an arrow function that will execute if the Promise is rejected or if an exception is thrown in the then function above. The function passed to catch takes a single parameter that I've called reason. Similar to the string data parameter above, this parameter will automatically receive the value passed to the reject function we saw in the last slide. Let's now go see Promises in action in a demo.
Demo: Creating and Using Promises
In this demo I'll take the code from the callback demo I showed you earlier and convert it to use Promises. Since Promises are a feature added to the ES2015 version of Javascript, I need to make sure the TypeScript compiler is targeting that version. Keep in mind that you'll need to make sure your JavaScript run-time environment supports ES2015 before making this change and attempting to implement Promises. All of my compiler options are stored here in a tsconfig.json file that exists at the root of my project directory. I'm just going to change the target parameter from ES5 to ES2015. That's all I need to change here, I'll now go over to app.ts. It currently contains all of the code from the last demo that used callback functions. I want to make sure I leave that code in here so you'll get it in the course download materials but I also want you to see the steps I go through to rewrite it to use Promises. To do that I'm just going to select all of it, copy it, and then comment it out right here. At the end of the file I'll paste a copy of it I can change to use Promises. Okay, since I won't be using callbacks in this demo I can get rid of the interface I created to define a function type for the callback function. I want to keep the getBooksByCategory function but the body of it is going to change enough that I'll just delete all of what's here now. Since I'm not using callbacks I don't need the second parameter to the function which was the callback. Instead of returning void, I'm going to change the function to now return a Promise. The string array type parameter specifies that when the returned Promise is successfully resolved it will contain an array of strings. The first thing I'm going to do in the body of the function is declare and create the new Promise that will ultimately be returned from the function. I'll simply name it p to keep my lines of code from getting too long. I'll declare it to have the same type that I just specified for the return value of the function, a Promise that resolves to an array of strings. I'll go ahead and assign it a new Promise. Notice the code completion help I'm getting in Visual Studio Code for the Promise constructor. It's a little hard to read because the constructor signature is so long but you can see that it expects a single function which is referred to here as executor, that function is passed two functions named resolve and reject. The code inside the executor function will perform the asynchronous work and then it will call either the resolve or reject functions depending on the result of that work. I also want to point out here the type parameter T. The ability to use type parameters with Promises is unique to TypeScript since JavaScript doesn't support that sort of thing. As with all use of types in TypeScript, it helps to make your code better by allowing the compiler to perform more checks for you. The T type parameter should represent the type of the value passed to the resolve function. In our example here that will be an array of strings. I'm going to implement the executor parameter as an arrow function. I'll specify that it will accept the resolve and reject functions and then implement the body of it inside curly braces. This is where we'll do some asynchronous work. The actual work I'm going to perform will be the same thing I did in the last demo with callbacks. I'm going to simulate a long running network call using the setTimeout function. I'll have it delay execution of the enclosed function by two seconds. I'll use a code snippet to paste in the code it will execute. You'll probably notice that this code looks very similar to the callback demo earlier. I make a call to the getBookTitlesByCategory function in the utility module and store the resulting string array in a new variable. I then check the length of that array, if it contains at least one element I call the resolve function that was passed into the executor function on the Promise constructor and pass it an array of stings containing the books that were found. If the array doesn't contain any data, I call the reject function and pass it the reason for the rejection. I could pass any data I want here but I'm just going to pass a string reporting that no books were found for that category. The last thing I need to do at the end of this function is to return the new Promise. Looking down a little further in the code we see the logCategorySearch function that I used as the callback in the last demo. I don't need that now since I'm using Promises so I'll just delete it. The last few lines here call the function and output the results. I'm going to leave the two surrounding console.log statements so we can see that the code is executing asynchronously and that we're not blocking anything. I need to change the call to getBooksByCategory since it now only takes one parameter which is the category I want to search for. I'll delete the callback I was passing as the second parameter. Okay, that looks much better. However, since I'm not passing in a callback to process the results, I need to handle that in a different way. I change getBooksByCategory to return a Promise. You register handlers for Promises using the then and catch functions on them. I'm going to remove the semicolon after the function and chain a call to then on a new line. The then function accepts a function as a parameter and that function accepts the parameter which will be the data passed to the resolve function in the case of a successful resolution of the Promise. I'm going to implement that with an arrow function. Since the resolve data will be an array of book titles, I'll name that parameter titles. I'll then just use a simple log statement to output those titles. There are a couple of ways to handle the Promise in the case that it's rejected. The first approach I'm going to show you and the one that I recommend is to call the catch function. The then function will automatically return another Promise, so I can just chain another call to a Promise instance method on it. The catch function has a similar signature to the then function. It takes a function whose parameter is the reason the Promise was rejected. I'll use an arrow function for it as well. I'll call the parameter reason and just log it to the console. The value automatically passed to the reason parameter will be the value passed to the reject function inside the Promise. Okay, I think I'm now ready to run this. I'm calling the function that will perform the work, it'll return a Promise and I'm using that Promise to register what should happen in both the success and error cases. In either case, the result should appear after the log statement below since the category search will execute asynchronously. I'll press F5 to run it. As expected, the Promise resolved successfully and the book titles were output a couple of seconds after that last log statement. Let's now try out the error case. I'll go back to the code and search for biographies. There aren't any books with that category so it should generate an error. I'll run it again. This time the catch function is called and the reason for the error is logged. Okay, I now want to show you how you can chain Promises together. I'll go back to the code and I'm going to change the code inside the then function. I'm going to wrap the arrow function inside curly braces so it can have multiple lines. I'm then going to add a return statement that will return the number of elements in the titles array. I realize this is a trivial example but I think it will very simply demonstrate what I want to show you. When you return a value from the function passed to then, that value becomes the resolve value for the Promise automatically returned from the then function. Therefore, I can now chain another call to then right below this and the value passed to the function it receives will be the number of books returned above. Again, I'll just log that value as well. Before I run it I'll change the category back to fiction so that I get some results. This time it outputs the titles as well as the number of books found. I mentioned earlier that there are a couple of ways you can handle rejected Promises. I'm using the catch function here but I want to at least quickly show you the alternative. You can actually pass a second parameter to the then functions to handle the rejections. It accepts a reason as the parameter just like the catch function I've already shown you. I'll add a second parameter to my first call to then. I'm not going to use the reason parameter, instead I'll just return zero which will be passed to the next call to then below. I'll change the category back to biography so we get an error. Notice that when I run this, the catch function is no longer called. The second function passed to then handles the error before it gets to the catch function. The catch function still plays a role though, in addition to handling rejected Promises, it will also handle any errors thrown in the then functions above it. I'll change the category back to fiction and then force an error to be thrown inside the first call to then. I'll run it one more time. You can see that this time the catch function did execute and output the error message I threw. Okay, let's now go back to the slides and see how we can take our asynchronous code to the next level with the async and await keywords.
Using async/await
Async and await are two additional TypeScript keywords that allow you to write asynchronous code in a more linear style. I'll show you what that looks like in just a minute but I think you'll agree that the result is much more readable than the other options we've seen. If you have a background in .NET development, you'll probably notice that async and await in TypeScript are very similar to async and await in C#. Under the hood, async and await are built using features added to the ES2015 version of JavaScript. They're built on top of Promises which we've already talked about, and generators and iterators which are a couple of other ES2015 features. Since these are ES2015 features, you cannot currently target the ES5 version of JavaScript with code that uses the async and await keywords. However, the TypeScript team is actively working on an alternative implementation of async/await that will let you target ES5. Let's now take a look at how you use these new keywords. On this slide I'm going to show you a very simple example of managing an asynchronous call with async/await. Here I've got a function named doAsyncWork, it makes a call to a function named GetDataFromServer. I'm not showing the code for it here but let's imagine that it returns a Promise. Since it returns a Promise, I can put the await keyword in front of it. The effect of this is that the code in the doAsyncWork function will pause on this line until the Promise returned from GetDataFromServer is resolved. That might sound like an undesirable blocking scenario but any function that uses the await keyword must be declared with the async keyword in front of the function declaration. This lets the compiler know that this function is performing asynchronous work and should not block other work from executing. Therefore, the execution inside the function will pause while waiting on the Promise to resolve but the doAsyncWork function itself will not block other code from executing. Here's a very simple example of how the function might be called. I log a message to the console and then call doAsyncWork. As soon as that call is made, the next console.log statement will execute even though the doAsyncWork function will likely still be paused waiting on data to be returned from the server. Let's now go rewrite the code from the previous demos to use async/await.
Demo: Writing Asynchronous Code with async/await
In this demo I'll convert the code from the previous demo that used Promises exclusively to a different implementation that takes advantage of the newer async and await keywords. I'm going to start this demo like I did the last one and make a copy of the previous code. I'll then comment it out, so you'll have it in the course download materials but I'll paste it down below so you can see how I'm going to modify it to use async and await. The first thing I'm going to do is delete all of the code that initiates the asynchronous work between my two console.log statements here at the bottom. Now I'm going to write a new async function that will call the getBooksByCategory function above. Since it will be an async function, the declaration begins with the async keyword. I'll name the function logSearchResults and it will take the category I want to search for as a parameter. I'll declare a new variable named foundBooks to store the array of titles returned from a call to getBooksByCategory. Since that function returns a Promise, I can use the await keyword in front of the function call to effectively pause execution inside this function until the Promise is resolved. I'm not going to change the getBooksByCategory function above, so it will still simulate a network call by waiting two seconds to return the found book titles. Once that happens, the code here will continue executing and I'll just have it log the titles to the console. I now just need to add a call to logSearchResults between my two log statements below. Just as we saw in the two earlier demos, we should see both of these log statements appear before the results of the search are shown. If that happens then we'll know we weren't blocking other work from happening while the long running call ran. I'll run the app, wait a couple of seconds, and we do get the expected results after both log statements. Let me now show you a couple of options you have for handling errors using this technique. One option is to wrap the code in the async function inside a try catch block. I'll use a code snippet to quickly show you what that might look like. I'm not doing anything fancy, I've still got the same two lines of code, they're just now wrapped in a try block and I can handle any errors that occur in the catch block. That would work fine but I'm going to undo that change and show you another option. Notice that if I hover over the logSearchResults declaration, we can see that it actually returns a Promise. All functions declared with the async keyword return Promises. If I had used a return statement inside the function body to return some value, you would see the type of that value listed as the type parameter for the Promise. Since I didn't return a value inside the function, this one just uses void for the type parameter. Since async functions return Promises, you can use the Promise methods we saw in the last demo to unwrap any values or errors they contain. I'll demonstrate that by chaining a call to catch onto the call to logSearchResults. It will be passed a reason if the Promise is rejected just like we saw in the last demo. I'll use a simple arrow function to log that reason. So that we can see this in action, I'll change the category I'm searching for to biography since I know I don't have any of those and run the code again. As expected, the reason is successfully logged to the console. I really like the more linear coding style that using the async and await keywords enables. I think the result is much more readable code that still includes all of the asynchronous benefits you get from the other techniques we've seen.
Summary
I'll be the first to admit that when you're cranking away on some code, it can be easy to concentrate so much on the specific requirements of a feature that you lose sight of the experience your users will have when they encounter that feature. User experience is a huge topic but carefully implementing asynchronous code in your applications is one small way you can improve it and keep your apps responsive and your users happy. In this module we first looked at callback functions and saw how we can pass them to the functions performing the asynchronous work. This technique has been around a long time in the JavaScript world but is still a perfectly valid way to manage asynchronous code. Next, we looked at Promises. I personally find them to be much cleaner than managing and passing around callback functions. Unfortunately, they were only added to JavaScript in the ES2015 version, so you still have to be careful about using them and make sure your targeted run-time supports them. Finally, we saw how to use the async and await keywords that are currently not available in JavaScript, although they are a proposed feature for a future version of the language. Using async/await builds on top of Promises and lets you write asynchronous code in a much more linear and readable style. All three of these techniques work great and which one you choose to implement may have as much to do with your own personal preference as much as making sure your choice will run where you intend to use it. Okay, in the next module I'm going to show you how to use a tool called TSLint to help you write better code and enforce coding standards on your team. Stay tuned for that.
Writing Cleaner Code with TSLint
Introduction and Overview
Hi, this is Brice Wilson. In this final module of the course, I'm going to show you how to use a utility named TSLint to help you write code with fewer errors that also conforms to a set of code styling standards you can configure. I'll start off this module with a brief explanation of TSLint and how you can use it as part of your TypeScript development process. I'll then quickly get into a demo and show you how to install TSLint, run it, and configure the linting rules it uses to alert you to problems. In a second demo, I'll show you how to install and use a really great extension for Visual Studio Code that will alert you to linting issues as you're writing your code.
What is TSLint?
Before I get any further into the specifics of using TSLint, I want to define what a linter is more generally in case it's a term you're not familiar with. The best way I can think to define a linter is that it's a program that runs and analyzes code for potential errors or deviation from accepted coding style guidelines. Linters are certainly not unique to TypeScript or JavaScript. They exist for lots of languages and are one more tool you can use to help you write the best bug-free code possible. So, more specifically, what is TSLint? It's obviously a linter, but it's also the most popular TypeScript linter, and it is used by thousands of developers. It's conceptually similar to popular JavaScript linters that you may have used before like JSLint and ESLint. It's really easy to get started with TSLint. You can install it with the node package manager, npm, and the rules it uses when linting your code are stored in an easily editable json file named tslint.json. Once you've got it installed and configured, you can run it from a command line. If you're using Visual Studio Code like I am, there's also an extension you can install that makes it even easier and quicker to find problems while you're still in your editor. There are also TSLint plugins for other editors. So if you're not using Visual Studio Code, a quick web search may reveal that something similar exists for your favorite editor. Okay, let's now get right into a demo so I can show you how to install and get started with TSLint.
Demo: Installing and Using TSLint
In this demo, I'll show you how to install and run TSLint as well as how to perform some simple tweaks to its default set of rules. I'm going to start out here on the TSLint website so you can see where to go to get more information about it. You can see the URL here in my browser. Everything on the site is pretty self-explanatory, I think. Notice at the top right of the page there are a few links. I specifically want to highlight the Rules link. Clicking on it takes you to a page that describes all of the rules that come with TSLint. They're presented on this page in different sections, and each rule name is a link that will take you to a page explaining exactly what the rule does and any options that are available for it. You'll definitely want to keep this page handy as you're configuring how you want each rule to work on your project. Back on the homepage, there's also a section showing you how to install TSLint with npm. It shows you how to install it locally for a specific project as well as globally if you want it available anywhere on your machine. I'll now jump over to a terminal window and run that command to install it on my machine. I just type npm install tslint. I'll also install typescript at the same time so that I have the latest version, and I'll use the -g option to install them both globally. That only takes a couple of seconds to run. Once it's done, I can run tslint from the command line. It includes several different command line arguments. To get a quick look at those, I'll first run it with the --help option. I'll pipe the output to the more command so the first part of the output doesn't scroll past. Here you can see some of the more common options you might use with the command. The --config option which can be abbreviated -c lets you specify the location of the tslint configuration file. And the --init option lets you create that configuration file if you're setting up a project with tslint for the first time. That's the option I need to use now with the library manager project. There's detailed help about all of these options further down in this output, but I'm going to exit out of this command, clear the screen, and create a new config file for my project. I'll just type tslint --init. I'll go back over to Visual Studio Code, and you can see that I have a new file added to the project. The default set of rules for tslint are stored in the tslint.json file. We'll take a closer look at some of the rules in just a second. First, I want to run tslint to see what kinds of things it finds. Back in my terminal, I'll type tslint followed by the name of the file I want it to analyze. I'll just have it look at app.ts. I could also use wildcards here to analyze more than one file at a time if I wanted to. I'll press Enter and all of the output is printed to my screen. Each line of the output represents one rule that's being violated. They start with the name of the file in the position in the file where you can find the problem. There's also a brief message telling you what's wrong. I'll scroll up just a little and you can see I've got several different errors here, but a couple of them are repeated over and over. It's obviously expecting new lines to be indented with spaces and reporting lots of problems since I am using Tabs in my code. A little further down are several instances where it's reporting that a single quote should be a double quote. Since those make up the majority of the problems, I'll focus on them first. I'm generally in the habit of indenting lines with Tabs, not spaces, and I normally quote strings with single quotes rather than double quotes. I want those to be the standards for this project, so I need to edit the default values that come with TSLint. I'll go back to Visual Studio Code and find those rules in the tslint.json file. Before I make those rule changes, I want to quickly point out how this file is organized. It begins with a rules section at the top. Each key in that section is the name of a different rule. Each key has a value that configures that rule. Some rules have options and some don't. If the rule doesn't have any options, the value is simply a Boolean specifying if the rule is enabled or not. For instance, the no-eval rule doesn't have any options, but it is currently enabled. Rules that do have options will have a json array for the value. The first element in the array is a Boolean specifying if the rule is enabled. The remaining elements set different options for the rule. You can find all of the available options on the TSLint website I showed you earlier. The indent rule here is one of the rules that does have a configurable option. Since the first element is true, I know the rule is currently enabled. I can also see that it's currently configured to expect indentation to be done with spaces. That's one of the reasons I got so many errors earlier. I'll change that to Tabs for my project. I'll scroll down just a bit and find the quotemark rule. It's enabled and set to double. Since I use single quotes, I'll change that value to single. I'll go back to my terminal and see if tweaking those rules got rid of some of my errors. I'll clear the screen and run tslint again. Okay, that looks much better. These remaining problems probably need to be fixed in my code. I've got a comment that needs to start with a space on line seven, a missing semicolon on line 13, and some missing whitespace on line 15. I'll now go try to find and fix those problems. I'll open up app.ts and then close the sidebar to give myself a little more room. The first problem was a missing space before the comment on line seven. You can see that this line doesn't have a space after the two forward slashes like the comment above it. I'll just add a space in there. Next was a missing semicolon on line 13. I'll fix that by adding one to the end of the line. The last error reported missing whitespace on line 15. That's referring to the lack of a space between the if keyword and the opening parenthesis around the condition. Adding a space there should clear that up. I'll now go back to my terminal and see if that fixed everything. I'll run the same command one more time. This time there was no output, so all of the code passed. That's the goal. In the next demo, I'll show you how to use an extension in Visual Studio Code to make this workflow a little smoother.
Demo: Using TSLint with Visual Studio Code
In this demo, I'll show you how to install and use the Visual Studio Code extension for TSLint. The first thing I need to do is find and install the TSLint extension. I can open the extensions view by clicking this bottom button on the left side of the window. It immediately shows me lots of popular extensions. I'm just going to type tslint in the search box at the top. It quickly finds the extension I'm looking for. This is a really great extension that I highly recommend you try out if you're using Visual Studio Code. It was developed by Erich Gamma, and you can see that it has many thousands of downloads and a five-star rating. Installing it only takes a second. I'll click the green install button, and once it's done, I'll click the blue Enable button. I'm told here that in order to enable it I need to restart Visual Studio Code. I'll click OK to go ahead and do that. That also only takes a second. I'm now ready to try it out. You can tell it was enabled by the appearance of the little TSLint button in the bottom right corner of the screen. I'll click on that, and it opens an output window that informs me the linter is running. I'll now purposely type a couple of mistakes so we can see it in action. I'll add a new console.log statement and leave off the semicolon at the end of the line. It may be a little hard to see, but I get a small green squiggly line at the end which alerts me to a problem. I'll hover over it, and a description of the rule I violated pops up, missing semicolon. I'll put my cursor at that position, and a little lightbulb appears next to it. I'll click on the lightbulb and the tslint extension offers to fix the problem for me. I'll click on that message and it inserts the semicolon for me at the end of the line. I love being able to find out about and fix problems as soon as I create them. That's much better than finding them later as part of a build step. I'll show you one more quick example. I'm going to type another console.log statement, but this time I'm going to indent the line with spaces. Again, you can see I've got a green squiggly line where TSLint found the error. I'll hover over this one and I'm told tab indentation expected. I'll delete the leading spaces and replace them with a Tab, and the error goes away. I know this was a really quick demo to show you some of the basic features, but the extension does lots of other cool tricks too. If you're using Visual Studio Code, definitely try it out. I think you'll like it.
Summary
I'm in favor of using any tool that seamlessly integrates into my workflow and helps me write better code. TSLint is definitely such a tool for TypeScript developers. In this module, I showed you how to install TSLint, edit some of its default linting rules, and how to integrate TSLint right into Visual Studio Code with an extension you can download right inside the editor. Okay, that wraps up this module as well as the course. I hope you've enjoyed it and learned lots of new things about TypeScript you can immediately apply in your own projects. Thanks for watching!
Course author
Brice Wilson
Brice has been a professional developer for over 20 years and loves to experiment with new tools and technologies. Web development and native iOS apps currently occupy most of his time.
Course info
LevelAdvanced
Rating
(137)
My rating
Duration2h 55m
Released15 Nov 2016
Share course