Mastering TypeScript - Second Edition

4.4 (7 reviews total)
By Nathan Rozentals
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. TypeScript - Tools and Framework Options

About this book

The TypeScript language, compiler, and opensource development toolset brings JavaScript development up to the enterprise level. It allows us to use ES5, ES6, and ES7 JavaScript language features today, including classes, interfaces, generics, modules, and more. Its simple typing syntax enables building large, robust applications using object-oriented techniques and industry standard design principles.

Packed with practical, real-world examples, this book is a guide to bringing the benefits of strongly typed, object-oriented programming and design principles into the JavaScript development space. Starting with core language features, and working through more advanced topics such as generics and asynchronous programming techniques, you will learn how to gain maximum benefit from your JavaScript development with TypeScript. With a strong focus on test-driven development, and coverage of many popular and in-demand JavaScript frameworks, you can fast-track your TypeScript knowledge to a professional level. By the end of this book, you will be able to confidently build TypeScript applications, whether you are targeting Angular, Aurelia, React, Backbone, Node, or any other JavaScript framework.

Publication date:
February 2017
Publisher
Packt
Pages
552
ISBN
9781786468710

 

Chapter 1. TypeScript - Tools and Framework Options

JavaScript is a truly ubiquitous language. Just about every website that you visit in the modern world is using JavaScript to make the site more responsive, more readable, or more attractive to use. Even traditional desktop applications are moving online. Where we once needed to download and install a program to generate a diagram, or write a document, we can now do all of this on the web, from within the confines of our humble browser.

This is the power of JavaScript. It enables us to rethink the way we use the web. But it also enables us to rethink the way we use web technologies. Node, for example, allows JavaScript to run server-side, rendering entire large scale web-sites, complete with session handling, load balancing, and database interaction. This shift in thinking about web technologies, however, is only the beginning.

Apache Cordova is a fully fledged web server that runs as a native mobile phone application. This means that we can build a mobile phone application using HTML, CSS, and JavaScript, and then interact with the phones accelerometer, geolocation services, or file storage. With Cordova, therefore, JavaScript and web technologies have moved into the realm of native mobile phone applications.

Likewise, projects such as Kinoma are using JavaScript to drive devices for the Internet Of Things, running on tiny microprocessors embedded in all sorts of devices. Espruino is a microcontroller chip purposefully designed to run JavaScript. Learning JavaScript, therefore, means that you have the ability to build websites, mobile phone applications, and even control microprocessors on embedded devices. JavaScript is becoming more and more popular, is being supported on more and more hardware, and is filtering through to nearly every corner of computing.

The JavaScript language is not a difficult language to learn, but it does present challenges when writing large, complex programs. One of these challenges is that JavaScript is an interpreted language and therefore has no compilation step. The only time you will know if you have made a simple syntax error is when you run the entire application through the run-time interpreter. Another challenge is that it is also not an object-oriented language, and it takes great care and discipline to build good, maintainable, and understandable JavaScript. For programmers that are moving from other object-oriented languages, such as Java, C#, or C++, JavaScript can seem like a completely foreign environment.

TypeScript bridges this gap. It is a strongly typed, object-oriented language that uses a compiler to generate JavaScript. It therefore allows us to use well known object-oriented techniques and design patterns to build JavaScript applications. Bear in mind that TypeScript-generated JavaScript is just plain JavaScript, and so will run wherever JavaScript can run - in the browser, on the server, on a mobile device, or on an embedded device.

This chapter is divided into two main sections. The first section is a quick overview of some of the benefits of using TypeScript, and the second section deals with setting up a TypeScript development environment.

If you are an experienced TypeScript programmer, and already have a development environment set up, then you might want to skip this chapter. If you have never worked with TypeScript before, and have picked up this book because you want to understand what TypeScript can do, then read on.

We will cover the following topics in this chapter:

  • The benefits of TypeScript

    • Compilation

    • Strong typing

    • Integration with popular JavaScript libraries

    • Encapsulation

    • Private and public member variables

  • Setting up a development environment

    • Visual Studio

    • WebStorm

    • Visual Studio Code

    • Other editors and grunt

 

Introducing TypeScript


TypeScript is both a language and a set of tools to generate JavaScript. It was designed by Anders Hejlsberg at Microsoft (the designer of C#), and is an open source project to help developers write enterprise-scale JavaScript.

TypeScript generates JavaScript - it's as simple as that. Instead of requiring a completely new runtime environment, TypeScript-generated JavaScript can reuse all of the existing JavaScript tools, frameworks, and the wealth of libraries that are already available for JavaScript. The TypeScript language and compiler, however, bring the development of JavaScript closer to a more traditional object-oriented experience.

The ECMAScript standard

JavaScript as a language has been around for a long time, and is governed by a language feature standard. The language defined in this standard is called ECMAScript, and each JavaScript interpreter must deliver functions and features that conform to this standard. The definition of this standard helped the growth of JavaScript and the web in general, and allowed websites to render correctly on many different browsers on many different operating systems. The ECMAScript standard was published in 1999 and is known as ECMA-262, third edition.

With the popularity of the language, and the explosive growth of Internet applications, the ECMAScript standard needed to be revised and updated. This revision process resulted in an updated draft specification for ECMAScript, called the fourth edition. Unfortunately, this draft also suggested a complete overhaul of the language, and therefore was not well received. Eventually, leaders from Yahoo, Google, and Microsoft tabled an alternate proposal, which they called ECMAScript 3.1. This proposal was numbered 3.1, as it was a smaller feature set of the third edition, and sat between edition three and four of the standard.

The proposal for a complete language overhaul was eventually adopted as the fifth edition of the standard, and was called ECMAScript 5. The ECMAScript fourth edition was never published, but it was decided to merge the best features of both the fourth edition and the 3.1 feature set into a sixth edition named ECMAScript Harmony.

The TypeScript compiler has a parameter that can switch between different versions of the ECMAScript standard. TypeScript currently supports ECMAScript 3, ECMAScript 5, ECMAScript 6, and even ECMAScript 7 (also known as ECMAScript 2016).

When the compiler runs over your TypeScript, it will generate compile errors if the code you are attempting to compile is not valid for that standard. The team at Microsoft has committed to following the ECMAScript standards in any new versions of the TypeScript compiler, so as new editions are adopted, the TypeScript language and compiler will follow suit.

An understanding of the finer details of what is included in each release of the ECMAScript standard is outside the scope of this book, but it is important to know that there are differences. Some browser versions do not support ES5 (IE8 is an example), but most do. When selecting a version of ECMAScript to target for your projects, you will need to consider which browser versions you will be supporting, or which standard your JavaScript runtime supports.

The benefits of TypeScript

To give you a flavor of the benefits of TypeScript (and this is by no means the full list), let's take a very quick look at some of the things that TypeScript brings to the table:

  • A compilation step

  • Strong or static typing

  • Type definitions for popular JavaScript libraries

  • Encapsulation

  • Private and public member variable decorators

Compiling

One of the most frustrating things about JavaScript development is the lack of a compilation step. JavaScript is an interpreted language, and therefore needs to be run against an interpreter in order to test that it is valid. Every JavaScript developer has horror stories that they can recount of hours spent trying to find bugs in their code, only to find that they have missed a stray closing brace { , or a simple comma , - or even a double quote " where there should have been a single quote ' . Even worse, the real headaches arrive when you misspell a property name, or unwittingly reassign a global variable.

TypeScript will compile your code, and generate compilation errors where it finds these sorts of syntax error. This is obviously very useful, and can help to highlight errors before the JavaScript is even run. In large projects, programmers will often need to do large code merges - and with today's tools doing automatic merges, it is surprising how often the compiler will pick up these types of errors.

While tools to do this sort of syntax checking like JSLint have been around for years, it is obviously beneficial to have these tools integrated into your development toolchain. Using the TypeScript compiler in a continuous integration environment will also fail a build completely when compilation errors are found, further protecting your code base against these types of bugs.

Strong typing

JavaScript is not strongly typed. It is a language that is very dynamic, and therefore allows objects to change their properties and behavior on-the-fly. As an example of this, consider the following code:

var test = "this is a string"; 
test = 1; 
test = function(a, b) { 
    return a + b; 
} 

On the first line of this code snippet, the variable test is bound to a string. It is then assigned to a number, and finally is redefined completely to be a function that expects two parameters. This means that the type of the variable test has changed from being a string to being a number, and then to being a function. Traditional object-oriented languages, however, will not allow the type of a variable to change, hence they are called strongly typed languages.

While all of the preceding code is valid JavaScript, and therefore could be justified, it is quite easy to see how this could cause runtime errors during execution. Imagine that you were responsible for writing a library function to add two numbers, and then another developer inadvertently reassigned your function to subtract these numbers instead.

These sorts of error may be easy to spot in a few lines of code, but it becomes increasingly difficult to find and fix these as your code base and your development team grows.

Another feature of strong typing is that the IDE you are working in understands what type of variable you are working with, and can bring better autocomplete or Intellisense options to the fore.

TypeScript's syntactic sugar

TypeScript introduces a very simple syntax to check the type of an object at compile time. This syntax has been referred to as "syntactic sugar", or more formally, type annotations. Consider the following TypeScript code:

    var test: string = "this is a string"; 
    test = 1; 
    test = function(a, b) { return a + b; } 

Note that, on the first line of this code snippet, we have introduced a colon : and a string keyword between our variable and its assignment. This type annotation syntax means that we are setting the type of our variable test to be of type string, and that any code that does not treat the variable test as a string will generate a compile error. Running the preceding code through the TypeScript compiler will generate two errors:

hello.ts(3,1): error TS2322: Type 'number' is not assignable to type 'string'.
hello.ts(4,1): error TS2322: Type '(a: any, b: any) => any' is not assignable 
to type 'string'.

The first error is fairly obvious. We have specified that the variable test is a string, and therefore attempting to assign a number to it will generate a compile error. The second error is similar to the first, and is, in essence, saying that we cannot assign a function to a string.

In this way, the TypeScript compiler introduces strong or static typing to your JavaScript code, giving you all of the benefits of a strongly typed language. TypeScript is therefore described as a superset of JavaScript. We will explore this in more detail in the next chapter.

JavaScript and TypeScript definitions

As we have seen, TypeScript has the ability to annotate JavaScript, and bring strong typing to the JavaScript development experience. But how do we strongly type existing JavaScript libraries? In other words, if we have an existing JavaScript library, how do we integrate this library for use within TypeScript? The answer is surprisingly simple--by creating a definition file. TypeScript uses files with a .d.ts extension as a sort of header file, similar to languages such as C++, to superimpose strongly typing on existing JavaScript libraries. These definition files hold information that describes each available function, and/or variables, along with their associated type annotations.

Let's take a quick look at what a definition would look like. As an example, consider the JavaScript describe function from the popular Jasmine unit testing framework, as follows:

    var describe = function(description, specDefinitions) { 
      return jasmine.getEnv().describe(description, specDefinitions); 
    }; 

Note that this function has two parameters--description and specDefinitions. Unfortunately JavaScript does not tell us what sort of variables these are. We would need to have a look at the Jasmine documentation to figure out how to call this function, and what variables are expected for both parameters. If we head over to http://jasmine.github.io/2.0/introduction.html, we will see an example of how to use this function:

    describe("A suite", function () { 
      it("contains spec with an expectation", function () { 
        expect(true).toBe(true); 
      }); 
    }); 

From the documentation, then, we can easily see that the first parameter is a string, and the second parameter is a function. However, there is nothing in JavaScript that forces us to conform to this API definition. As mentioned before, we could easily call this function incorrectly, with two numbers for example, or by sending a function first and a string second. Making mistakes like these will obviously generate runtime errors. Using a simple TypeScript definition file, however, will generate compile-time errors before we even attempt to run this code.

Let's take a look at the corresponding TypeScript definition for this function, found in the jasmine.d.ts definition file:

    declare function describe( 
      description: string,  
      specDefinitions: () => void 
    ): void; 

Here, we have the TypeScript definition for the Jasmine describe function. This definition looks very similar to the function itself, but gives us a little more information about the parameters.

Clearly, the description parameter is strongly typed to a string, and the specDefinitions parameter is strongly typed to be a function that returns void. TypeScript uses the double-brace () syntax to declare functions, and the arrow syntax => to show the return type of the function. So () => void is a function that does not return anything. Finally, the describe function itself will also return void.

If our code were to try and pass in a function as the first parameter, and a string as the second parameter (clearly breaking the definition of this function), as follows:

    describe(() => { /* function body */}, "description"); 

TypeScript would generate the following error:

hello.ts(11,11): error TS2345: Argument of type '() => void' is not assignable to parameter of type 'string'.

This error is telling us that we are attempting to call the describe function with invalid parameters. We will take a look at definition files in more detail in later chapters, but this example clearly shows that the TypeScript compiler will generate errors if we attempt to use external JavaScript libraries incorrectly.

DefinitelyTyped

Soon after TypeScript was released, Boris Yankov started a GitHub repository to house definition files, called DefinitelyTyped (http://definitelytyped.org). This repository has now become the first port of call for integrating external JavaScript libraries into TypeScript, and it currently holds definitions for over 1,600 JavaScript libraries.

Encapsulation

One of the fundamental principles of object-oriented programming is encapsulation--the ability to define data, as well as a set of functions that can operate on that data, into a single component. Most programming languages have the concept of a class for this purpose, providing a way to define a template for data and related functions.

Let's first take a look at a simple TypeScript class definition, as follows:

    class MyClass { 
      add(x, y) { 
        return x + y; 
      } 
    } 
 
    var classInstance = new MyClass(); 
    var result = classInstance.add(1,2); 
    console.log(`add(1,2) returns ${result}`); 

This code is pretty simple to read and understand. We have created a class, named MyClass, with a simple add function. To use this class we simply create an instance of it, and call the add function with two arguments.

JavaScript, unfortunately, does not have a class statement, but instead uses functions to reproduce the functionality of classes. Encapsulation through classes is accomplished by either using the prototype pattern, or by using the closure pattern. Understanding prototypes and the closure pattern, and using them correctly, is considered a fundamental skill when writing enterprise-scale JavaScript.

A closure is essentially a function that refers to independent variables. This means that variables defined within a closure function remember the environment in which they were created. This provides JavaScript with a way to define local variables, and provide encapsulation. Writing the MyClass definition in the preceding code, using a closure in JavaScript, would look something like the following:

    var MyClass = (function () { 
      // the self-invoking function is the 
      // environment that will be remembered 
      // by the closure 
      function MyClass() { 
      // MyClass is the inner function, 
      // the closure 
      } 
      MyClass.prototype.add = function (x, y) { 
        return x + y; 
      }; 
      return MyClass; 
    }()); 
    var classInstance = new MyClass(); 
    var result = classInstance.add(1, 2); 
    console.log("add(1,2) returns " + result);  

We start with a variable called MyClass, and assign it to a function that is executed immediately--note the })(); syntax near the bottom of the closure definition. This syntax is a common way to write JavaScript in order to avoid leaking variables into the global namespace. We then define a new function named MyClass, and return this function to the outer calling function. We then use the prototype keyword to inject another function into the MyClass definition. This function is named add and takes two parameters, returning their sum.

The last few lines of the code show how to use this closure in JavaScript. Create an instance of the closure type, and then execute the add function. Running this code will log add(1,2) returns 3 to the console, as expected.

Looking at the JavaScript code versus the TypeScript code, we can easily see how simple the TypeScript code looks compared to the equivalent JavaScript. Remember how we mentioned that JavaScript programmers can easily misplace a brace { or a bracket ( ? Have a look at the last line in the closure definition--})();. Getting one of these brackets or braces wrong can take hours of debugging to find.

TypeScript classes generate closures

The JavaScript, as shown above, is actually the output of the TypeScript class definition. So TypeScript actually generates closures for you.

Note

Adding the concept of classes to the JavaScript language has been talked about for years, and is currently a part of the ECMAScript sixth edition (Harmony) standard - but this is still a work in progress. Microsoft has committed to following the ECMAScript standard in the TypeScript compiler, as and when these standards are published.

Public and private accessors

A further object oriented principle that is used in Encapsulation is the concept of data hiding--the ability to have public and private variables. Private variables are meant to be hidden from the user of a particular class, as these variables should only be used by the class itself. Inadvertently exposing these variables can easily cause runtime errors.

Unfortunately, JavaScript does not have a native way of declaring variables private. While this functionality can be emulated using closures, a lot of JavaScript programmers simply use the underscore character _ to denote a private variable. At runtime, though, if you know the name of a private variable, you can easily assign a value to it. Consider the following JavaScript code:

    var MyClass = (function() { 
        function MyClass() { 
            this._count = 0; 
        } 
        MyClass.prototype.countUp = function() { 
          this._count ++; 
        } 
        MyClass.prototype.getCountUp = function() { 
          return this._count; 
        } 
        return MyClass; 
    }()); 
 
    var test = new MyClass(); 
    test._count = 17; 
    console.log("countUp : " + test.getCountUp()); 

The MyClass variable is actually a closure with a constructor function, a countUp function, and a getCountUp function. The variable _count is supposed to be a private member variable that is used only within the scope of the closure. Using the underscore naming convention gives the user of this class some indication that the variable is private, but JavaScript will still allow you to manipulate the variable _count. Take a look at the second last line of the code snippet. We are explicitly setting the value of _count to 17,which is allowed by JavaScript, but not desired by the original creator of the class. The output of this code would be countUp : 17.

TypeScript, however, introduces public and private keywords (among others), which can be used on class member variables. Trying to access a class member variable that has been marked as private will generate a compile time error. As an example of this, the JavaScript code above can be written in TypeScript, as follows:

    class CountClass { 
      private _count: number; 
        constructor() { 
          this._count = 0; 
        } 
      countUp() { 
        this._count ++; 
      } 
      getCount() { 
        return this._count; 
      } 
    } 
    var countInstance = new CountClass() ; 
    countInstance._count = 17; 

On the second line of our code snippet, we have declared a private member variable named _count. Again, we have a constructor, a countUp, and a getCount function. If we compile this file, the compiler will generate an error:

hello.ts(39,15): error TS2341: Property '_count' is private and only
accessible within class 'CountClass'. 

This error is generated because we are trying to access the private variable _count in the last line of the code.

The TypeScript compiler, therefore, is helping us to adhere to public and private accessors by generating a compile error when we inadvertently break this rule.

Tip

Remember, though, that these accessors are a compile-time feature only, and will not affect the generated JavaScript. You will need to bear this in mind if you are writing JavaScript libraries that will be consumed by third parties. Note that by default, the TypeScript compiler will still generate the JavaScript output file, even if there are compile errors. This option can be modified, however, to force the TypeScript compiler not to generate JavaScript if there are compilation errors.

 

TypeScript IDEs


The purpose of this section of the chapter is to get you up-and-running with a TypeScript environment so that you can edit, compile, run, and debug your TypeScript code. TypeScript has been released as open-source, and includes both a Windows variant, as well as a Node variant. This means that the compiler will run on Windows, Linux, OS X, and any other operating system that supports Node. On Windows environments, we can either install Visual Studio, which will register the tsc.exe (TypeScript compiler) in our c:\Program Files directory, or we can use Node. On Linux and OS X environments, we will need to use Node.

In this section, we will be looking at the following IDEs:

  • Node-based compilation

  • Visual Studio 2015

  • WebStorm

  • Visual Studio Code

  • Using grunt

Node-based compilation

The simplest and leanest TypeScript development environment consists of a simple text editor, and a Node-based TypeScript compiler. Head over to the Node website (https://nodejs.org/) and follow the instructions to install Node on your operating system of choice.

Once Node is installed, TypeScript can be installed by simply typing:

npm install -g typescript

This command invokes the Node Package Manager (npm) to install TypeScript as a global module (the -g option), which will make it available no matter what directory we are currently in. Once TypeScript has been installed, we can display the current version of the compiler by typing the following:

tsc -v

At the time of writing, the TypeScript compiler is at version 2.1.5, and therefore the output of this command is as follows:

Version 2.1.5

Let's now create a TypeScript file named hello.ts, with the following content:

    console.log('hello TypeScript'); 

From the command line, we can use TypeScript to compile this file into a JavaScript file by issuing the command:

tsc hello.ts

Once the TypeScript compiler has completed, it will have generated a hello.js file in the current directory.

Creating a tsconfig.json file

The TypeScript compiler uses a tsconfig.json file at the root of the project directory to specify any global TypeScript project settings and compiler options. This means that, instead of compiling our TypeScript files one by one (by specifying each file on the command line), we can simply type tsc from the project root directory, and TypeScript will recursively find and compile all TypeScript files within the root directory and all sub-directories. The tsconfig.json file that TypeScript needs in order to do this can be created from the command line by simply typing:

tsc --init

The result of this command is a basic tsconfig.json file as follows:

    {
      "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "noImplicitAny": false,
        "sourceMap": false
       }
    } 

This is a simple JSON format file, with a single JSON property named compilerOptions, which specifies compile options for the project. The target property indicates the preferred JavaScript output to generate, and can be either es3, es5, es6, ES2016, ES2017, or ESNext. The option named sourceMap is a flag indicating whether to generate source maps that are used for debugging. The noImplicitAny option is a flag indicating that we must attempt to strongly type all variables before use.

Note

TypeScript allows for multiple tsconfig.json files within a directory structure. This allows different sub directories to use different compiler options.

With our tsconfig.json file in place, we can compile our application by simply typing:

tsc

This command will invoke the TypeScript compiler, using the tsconfig.json file that we have created to generate a hello.js JavaScript file. In fact, any file TypeScript source file that has a file extension of .ts will generate a JavaScript file with an extension of .js. We can now run our application by typing:

node hello.js

As our application is simply logging some text to the command line, the output will be as follows:

λ node hello.js
hello TypeScript

We have successfully created a simple Node-based TypeScript development environment, with a simple text editor and access to the command line.

Microsoft Visual Studio

Let's now look at Microsoft's Visual Studio. This is Microsoft's primary IDE, and comes in a variety of pricing combinations. At the time of writing, Microsoft had just released Visual Studio 2017 Release Candidate, as the successor to Visual Studio 2015. Microsoft has an Azure-based licensing model, starting at around $45 per month, all the way up to a professional license with an MSDN subscription at around $1,199. The good news is that Microsoft also has a Community edition, which can be used in non-enterprise environments for both free and non-paid products. The TypeScript compiler is included in all of these editions.

Visual Studio can be downloaded as either a web installer or an .iso CD image. Note that the web installer will require an Internet connection during installation, as it downloads the required packages during the installation step. Visual Studio will also require Internet Explorer 10 or later, but will prompt you during installation if you have not upgraded as yet. If you are using the .iso installer, just bear in mind that you may be required to download and install additional operating system patches if you have not updated your system in a while.

Creating a Visual Studio project

Once Visual Studio 2017 is installed, fire it up and create a new project (File | New Project). There are many different options available for new project templates, depending on your choice of language. Under the Templates section on the left-hand side, you will see an Other Languages option, and under this a TypeScript option. The project templates that are available are slightly different in Visual Studio 2017 than they are in Visual Studio 2015, and are geared towards Node development.

Visual Studio 2015 has a template named Html Application with TypeScript, which will create a very simple, single-page Web application for you. Unfortunately, this option has been removed in Visual Studio 2017 as shown in the following screenshot:

Visual Studio 2017 - TypeScript project templates

To create a simple TypeScript web application in Visual Studio 2017, we will need to create a blank web application first, and then we can add TypeScript files to this project as need be. From our Templates dialog, then, select the Visual C# template option, and then select the Web option. This will give us a project template named ASP.NET Web Application. Select a Name and a Location for the new project, and then click on OK, as shown in the following screenshot:

Visual Studio 2017 - creating an ASP.NET web application

Once we have selected the basic information for our new project, Visual Studio will generate a second dialog box asking what sort of ASP.NET project we would like to generate. Select the Empty template, and click on OK, as shown below:

Visual Studio 2017 - options for creating an ASP.NET web application

Visual Studio 2017 will then pop up another dialog named Create App Service, which provides options for creating a host in Azure for your new web application. We will not be publishing our application to Azure, so we can click on Skip at this stage.

Default project settings

Once a new Empty ASP.NET web application has been created, we can start adding files to the project by right-clicking on the project itself and selecting Add then New Item. There are two files that we are going to add to the project, namely an index.html file and an app.ts TypeScript file. For each of these files, select the corresponding Visual Studio template, as follows:

Visual Studio - adding a TypeScript file

We can now open up the app.ts file, and start typing the following code:

    class MyClass { 
      public render(divId: string, text: string) { 
        var el: HTMLElement = document.getElementById(divId); 
        el.innerText = text; 
      } 
    } 
 
    window.onload = () => { 
      var myClass = new MyClass(); 
      myClass.render("content", "Hello World"); 
    }; 

Here, we have created a class named MyClass, that has a single render function. This function takes two parameters, named divId and text. The function finds an HTML DOM element that matches the divId argument, and then sets the innerText property to the value of the text argument. We then define a function to be called when the browser calls window.onload. This function creates a new instance of the MyClass class, and calls the render function.

Note

Do not be alarmed if this syntax and code is a little confusing. We will be covering all of the language elements and syntax in later chapters. The point of this exercise is simply to use Visual Studio as a development environment for editing TypeScript code.

You will notice that Visual Studio has very powerful Intellisense options, and will suggest code, function names, or variable names as and when you are typing your code. If they are not automatically appearing, then hitting Ctrl-Space will bring up the Intellisense options for the code you are currently typing.

With our app.ts file in place, we can compile it by hitting Ctrl-Shift- B, or F6, or by selecting the Build option from the toolbar. If there are any errors in the TypesScript code that we are compiling, Visual Studio will automatically pop up an Error List panel, showing current compilation errors. Double-clicking on any one of these errors will bring up the file in the editor panel, and automatically move the cursor to the offending code.

Note

The generated app.js file is not included in the Solution Explorer in Visual Studio. Only the app.ts TypeScript file is included. This is by design. If you wish to see the generated JavaScript file, simply click on the Show All Files button in the Solution Explorer toolbar.

To include our TypeScript file in the HTML page, we will need to edit the index.html file, and add a <script> tag to load app.js, as follows:

    <!DOCTYPE html> 
    <html> 
    <head> 
      <meta charset="utf-8" /> 
      <title></title> 
      <script src="app.js"></script> 
    </head> 
    <body> 
      <div id="content"></div> 
    </body> 
    </html> 

Here, we have added the <script> tag to load our app.js file, and have also created a <div> element with the id of content. This is the DOM element that our code will modify the innerHtml property of. We can now hit F5 to run our application:

Visual Studio index.html running in Chrome

Debugging in Visual Studio

One of the best features of Visual Studio is that it is truly an integrated environment. Debugging TypeScript in Visual Studio is exactly the same as debugging C# or any other language in Visual Studio, and includes the usual Immediate, Locals, Watch, and Call stack windows.

To debug TypeScript in Visual Studio, simply put a breakpoint on the line you wish to break on in your TypeScript file (move your mouse into the breakpoint area next to the source code line, and click). In the following screenshot, we have placed a breakpoint within the window.onload function. To start debugging, simply hit F5:

Visual Studio TypeScript editor with a breakpoint set in the code

When the source code line is highlighted in yellow, hover your mouse over any of the variables in your source, or use the Immediate, Watch, Locals, or Call stack windows.

Tip

Note that Visual Studio only supports debugging in Internet Explorer 11. If you have multiple browsers installed on your machine (including Microsoft Edge), make sure that you select Internet Explorer in your Debug toolbar, as shown in the following screenshot:

Visual Studio Debug toolbar showing browser options

WebStorm

WebStorm is a popular IDE by JetBrains (http://www.jetbrains.com/webstorm/), and will run on Windows, Mac OS X, and Linux. Prices range from $59 per year for a single developer to $129 per year for a commercial license. JetBrains also offers a 30-day trial version.

WebStorm has a couple of great features, including live edit and code suggestions, or Intellisense. Live edit allows you to keep a browser open that will automatically update based on changes to CSS, HTML, and JavaScript as you type it. Code suggestions, which are also available with another popular JetBrains product, ReSharper, will highlight code that you have written and suggest better ways of implementing it. WebStorm also has a large number of project templates. These templates will automatically download and include the relevant JavaScript or CSS files, such as Twitter, Bootstrap, or HTML5 boilerplate.

On Windows systems, setting up WebStorm is as simple as downloading the package from the website, and running the installer. On Linux systems, Webstorm is provided as a tar ball. Once it's unpacked, install WebStorm by running the webstorm.sh script in the bin directory. Note that on Linux systems, a running version of Java must be installed before setup will continue.

Creating a WebStorm project

To create a WebStorm project, fire up WebStorm and hit File | New Project. Select a name, location, and project type. For this project, we have selected Twitter Bootstrap:

WebStorm new project dialog box

Default files

WebStorm has conveniently created css and js directories as part of the new project and downloaded and included the relevant CSS and JavaScript files for us to start building a new Bootstrap-based site. Note that it has not created an index.html file for us, nor has it created any TypeScript files. So let's create an index.html file.

Simply click on File | New, select HTML file, enter index as a name, and click OK.

Next, let's create a TypeScript file in a similar manner. We will call this file app (or app.ts), in order to mirror the Visual Studio project. As we click inside the new app.ts file, WebStorm will pop up a message at the top of the file, with a suggestion reading Compile TypeScript to JavaScript? with three options--OK, No, and Configure, as shown in the following screenshot:

WebStorm editing a TypeScript file for the first time showing the file watcher bar

Clicking on Configure will bring up the Settings panel for TypeScript. Click on the Enable TypeScript compiler checkbox to enable modifications to the settings, then click on the Use tsconfig.json radio button, and click OK. WebStorm is now configured to use the tsconfig.json file in the projects root directory. As this file does not yet exist, a TypeScript error panel will open, indicating that the compiler cannot find tsconfig.json in the project. To fix this error, we will need to create a tsconfig.json file, so click on File | New, and type tsconfig.json as the filename. Switch back to the app.ts file, hit Ctrl-S to save, and the error message will disappear.

Building a simple HTML application

Now that we have configured WebStorm to compile our Typescript files, let's create a simple TypeScript class and use it to modify the innerText property of an HTML div. While you are typing, you will notice WebStorm's auto-completion or Intellisense feature helping you with available keywords, parameters, naming conventions, and so on. This is one of the most powerful features of WebStorm, and is similar to the enhanced Intellisense seen in Visual Studio. Go ahead and type the following TypeScript code, during which you will get a good feeling for WebStorm's available auto-completion:

    class MyClass { 
      public render(divId: string, text: string) { 
        var el: HTMLElement = document.getElementById(divId); 
        el.innerText = text; 
      } 
    } 
 
    window.onload = () => { 
      var myClass = new MyClass(); 
      myClass.render("content", "Hello World"); 
    } 

This code is the same as we used in the Visual Studio example.

If you have any errors in your TypeScript file, these will automatically show up in the output window, giving you instant feedback while you type. With this TypeScript file created, we can now include it in our index.html file, and try some debugging.

Open the index.html file, and add a script tag to include the app.js JavaScript file, along with a div with an id of "content". Just as we saw with TypeScript editing, you will find that WebStorm has powerful Intellisense features when editing HTML as well:

    <!DOCTYPE html> 
    <html> 
    <head lang="en"> 
    <meta charset="UTF-8"> 
    <title></title> 
    <script src="app.js"></script> 
    </head> 
    <body> 
      <div id="content"></div> 
    </body> 
    </html> 

Again, this HTML is the same as we used earlier in the Visual Studio example.

Running a web page in Chrome

When viewing or editing HTML files in WebStorm, you will notice a small set of browser icons popping up in the top-right corner of the editing window. Clicking on any one of the icons will launch your current HTML page using the selected browser:

WebStorm editing an HTML file showing popup browser-launching icons

Debugging in Chrome

To debug our web application in WebStorm, we will need to set up a debug configuration for the index.html file. Click on Run | Debug and then edit configurations. Click on the plus (+) button, select the JavaScript debug option on the left, and give this configuration a name. Note that WebStorm has already identified that index.html is the default page, but this can easily be modified. Next, click on Debug at the bottom of the screen, as shown in the following screenshot:

WebStorm debugging configuration for index.html.

WebStorm uses a Chrome plugin to enable debugging in Chrome and will prompt you the first time you start debugging to download and enable the JetBrains IDE Support Chrome plugin. With this plugin enabled, WebStorm has a very powerful set of tools to inspect JavaScript code, add watchers, view the console, and many more, right inside the IDE.

WebStorm debugging session showing debugger panels

Visual Studio Code

Visual Studio Code is a lightweight development enviroment produced by Microsoft that runs on Windows, Linux, and Mac. It includes development features such as syntax highlighting, bracket matching, Intellisense, and also has support for many different languages. These languages include TypeScript, JavaScript, JSON, HTML, CSS, C#, C++ and many more - making it ideal for TypeScript development in either web pages or Node. Its main focus is currently ASP.NET development with C#, and Node development with TypeScript. It has also been built with strong git support out-of-the-box.

Installing VSCode

VSCode can be installed on Windows by simply downloading and running the installer. On Linux systems, VSCode is provided as a .deb package, an .rpm package, or a binary tar file. Under Mac, download the .zip file, unzip it, and then copy the Visual Studio Code.app file to your applications folder.

Exploring VSCode

Create a new directory to hold your source code and fire up VSCode. This can be done by navigating to the directory and executing code . from the command line. On Windows systems, fire up VSCode, and then Select File | Open folder from the menu bar. Hit Ctrl - N to create a new file, and type the following:

    console.log("hello vscode"); 

Note that there is no syntax highlighting at this stage, as VSCode does not know what type of file it is working with. Hit Ctrl - S to save the file, and name it hello.ts. Now that VSCode understands this to be a TypeScript file, you will have full Intellisense and syntax highlighting available.

Creating a tasks.json file

The keyboard shortcut to build a project in VSCode is Ctrl - Shift - B. If we try to build the project at this stage, VSCode will show a message--No task runner configured, and give us the option to Configure Task Runner. We can then select which sort of task runner we would like to configure, including Grunt, Gulp, and a number of other options. Selecting one of these options will automatically create a tasks.json file for us in the .vscode directory, and open it for editing.

As an example of this let's select the TypeScript - tsconfig.json option. We will make a single change to the generated tsconfig.json file, and set the value of the "showOutput" option to "always", instead of "silent". This will force VSCode to open an output window whenever it sees compilation issues.

Our tasks.json file now contains the following:

    // A task runner that calls the Typescript compiler (tsc) and 
    // compiles based on a tsconfig.json file that is present in 
    // the root of the folder open in VSCode 
    { 
      "version": "0.1.0", 
      "command": "tsc", 
      "isShellCommand": true, 
      "showOutput": "always", 
      "args": ["-p", "."], 
      "problemMatcher": "$tsc" 
    } 
Building the project

Our sample project can now be built by hitting Ctrl - Shift - B. Note that in the base directory of our project, we now have a hello.js and a hello.js.map file as the result of the compilation step.

Creating a launch.json file

VSCode includes an integrated debugger that can be used to debug TypeScript projects. If we switch to the Debugger panel, or simply hit F5 to start debugging, VSCode will ask us to select a debugging environment. For the time being, select the Node.js option, which will create a launch.json file in the .vscode directory, and again open it for editing. Find the option named "program", and modify it to read "${workspaceRoot}/hello.js". Hit F5 again, and VSCode will launch hello.js as a Node program and output the results to the debugging window:

node --debug-brk=34146 --nolazy hello.js 
debugger listening on port 34146
hello vscode
Setting breakpoints

Using breakpoints and debugging at this stage will only work on the generated .js JavaScript files. We will need to make another change to the launch.json file to enable debugging directly in our TypeScript files. Edit the launch.json file, and change the "sourceMaps" : false property to true. Now we can set breakpoints directly in our .ts files for use by the VSCode debugger:

Debugging a Node application within Visual Studio Code

Debugging web pages

Debugging TypeScript running within a web page in VSCode takes a little more setup. VSCode uses the Chrome debugger to attach to a running web page. To enable debugging web pages, we will firstly need to modify the launch.json file and add a new launch option, as follows:

    "configurations": [ 
      { 
        "name": "Launch", 
        ... 
      }, 
      {   
        "name": "Attach 9222", 
        "type": "chrome", 
        "request": "attach", 
        "port": 9222, 
        "sourceMaps": true 
      } 
    ] 

This launch option is named "Attach 9222", and will attach to a running instance of chrome using the debug port 9222. Save the launch.json file, and create an HTML page named index.html at the root directory of the project, as follows:

    <html> 
      <head> 
      <script src="helloweb.js"></script> 
      </head> 
      <body> 
        hello vscode 
        <div id="content"></div> 
      </body> 
    </html> 

This is a very simple page that loads the helloweb.js file, and displays the text hello vscode. Our helloweb.ts file is as follows:

    window.onload = () => { 
      console.log("hello vscode"); 
    }; 

This TypeScript code simply waits for the web page to load, and then logs "hello vscode" to the console.

The next step is to fire up Chrome using the debug port option. On Linux systems, this is done from the command prompt, as follows:

google-chrome --remote-debugging-port=9222

Note that you will need to ensure that there are no other instances of Chrome running in order to use it as a debugger with VSCode.

Next, load the index.html file in the browser by using the file://<full_path_to_file>/index.html syntax. You should see the HTML file rendering the "hello vscode" text.

Now we can go back to VSCode, click on the debugging icon, and select the Attach 9222 option in the launcher drop-down. Hit F5, and the VSCode debugger should now be attached to the running instance of Chrome. We will then need to refresh the page in Chrome in order to start debugging:

Debugging web pages in Visual Studio Code

With a slight tweak to our launch.json, we can combine these manual steps into a single launcher, as follows:

    { 
        "name": "Launch chrome", 
        "type": "chrome", 
        "request" : "launch", 
        "url" : "file:/// ... insert full path here ... /index.html", 
        "runtimeArgs": [ 
            "--new-window", 
            "--remote-debugging-port=9222" 
        ], 
        "sourceMaps": true 
    } 

In this launch configuration, we have changed the request property from "attach" to "launch", which will launch a new instance of Chrome and automatically navigate to the file path specified in the "url" property. The "runtimeArgs" property now also specifies the remote debugging port of 9222. With this launcher in place, we can simply hit F5 to launch Chrome, with the correct URL and debugging options for debugging of HTML applications.

Other editors

There are a number of editors that include support for TypeScript, such as Atom, Brackets, and even the age-old Vim editor. Each of these editors has varying levels of TypeScript support, including syntax highlighting and Intellisense. Using these editors represents a bare-bones TypeScript development environment, relying on the command line to automate build tasks. They do not have built-in debugging tools, and therefore do not qualify as an Integrated Development Environment (IDE) per se, but can easily be used to build TypeScript applications. The basic workflow using these editors would be as follows:

  • Create and modify files using the editor

  • Invoke the TypeScript compiler from the command line

  • Run or debug applications using existing debuggers

Using Grunt

In a bare-bones environment, any change to a TypeScript file means that we need to re-issue the tsc command from the command line every time we wish to compile our project. Obviously, it is going to be very tedious to have to switch to the command prompt and manually compile our project every time we have made a change. This is where tools like Grunt come in handy. Grunt is an automated task runner (http://gruntjs.com) that can automate many tedious compile, build, and test tasks. In this section, we will use Grunt to watch our TypeScript files, and automatically invoke the tsc compiler when a file is saved.

Grunt runs in a Node environment, and therefore needs to be installed as an npm dependency of your project. It cannot be installed globally as most npm packages can. In order to do this, we will need to create a packages.json file in the root project. Open up a command prompt, and navigate to the root directory of your project. Then, simply type:

npm init

Then follow the prompts. You can pretty much leave all of the options at their defaults, and always go back to edit the packages.json file that is created from this step, should you need to tweak any changes.

Now we can install Grunt. Grunt has two components that need to be installed independently. Firstly, we need to install the Grunt command-line interface, which allows us to run Grunt from the command line. This can be accomplished as follows:

npm install -g grunt-cli

The second component is to install the grunt files within our project directory:

npm install grunt --save-dev

The --save-dev option will install a local version of Grunt in the project directory. This is done so that multiple projects on your machine can use different versions of Grunt. We will also need the grunt-exec package, as well as the grunt-contrib-watch package. These can be installed with the following commands:

npm install grunt-exec --save-dev
npm install grunt-contrib-watch --save-dev.

Lastly, we will need a GruntFile.js. Using an editor, create a new file, save it as GruntFile.js, and enter the following JavaScript. Note that we are creating a JavaScript file here, not a TypeScript file. You can find a copy of this file in the sample source code that accompanies this chapter:

    module.exports = function (grunt) { 
      grunt.loadNpmTasks('grunt-contrib-watch'); 
      grunt.loadNpmTasks('grunt-exec'); 
      grunt.initConfig( { 
        pkg: grunt.file.readJSON('package.json'), 
        watch : { 
            files : ['**/*.ts'], 
            tasks : ['exec:run_tsc'] 
        }, 
        exec: { 
            run_tsc: { cmd : 'tsc'} 
        } 
      }); 
      grunt.registerTask('default', ['watch']); 
    }; 

This GruntFile.js contains a simple function to initialize the Grunt environment, and specify the commands to run. The first two lines of the function are loading grunt-contrib-watch and grunt-exec as npm tasks. We then call initConfig to configure the tasks to run. This configuration section has a pkg property, a watch property, and an exec property. The pkg property is used to load the package.json file that we created earlier as part of the npm init step.

The watch property has two sub-properties. The files property specifies what to watch for, in this case any .ts files in our source tree, and the tasks array specifies that we should kick off the exec:run_tsc command once a file has changed. Finally, we call grunt.registerTask, specifying that the default task is to watch for file changes.

We can now run grunt from the command line, as follows:

grunt

As can be seen from the command line output, Grunt is running the watch task, and is waiting for changes to any .ts files:

Running "watch" task
Waiting...

Open up any TypeScript file, make a small change (add a space or something), and then hit Ctrl-S to save the file. Now check back on the output from the Grunt command line. You should see something like the following:

>> File "hellogrunt.ts" changed.
Running "exec:run_tsc" (exec) task
Done, without errors.
Completed in 2.008s at Sat Mar 19 2016 20:27:17 GMT+0800
 (W. Australia Standard Time) - Waiting...

This command line output is a confirmation that the Grunt watch task has identified that the hellogrunt.ts file has changed, run the exec:run_tsc task, and is waiting for the next file to change. We should now also see a hellogrunt.js file in the same directory as our Typescript file.

 

Summary


In this chapter, we have had a quick look at what TypeScript is and what benefits it can bring to the JavaScript development experience. We also looked at setting up a development environment using some popular IDEs, and had a look at what a bare-bones development environment would look like. Now that we have a development environment set up, we can start looking at the TypeScript language itself in a bit more detail. We will start with types, move on to variables, and then discuss functions in the next chapter.

About the Author

  • Nathan Rozentals

    Nathan Rozentals has been writing commercial software for over 23 years. Starting with COBOL on mainframes, through C, onto C++, Java and finally settling on C# and ASP.NET.

    Nathan picked up TypeScript in October 2012 - a day after the 0.8.0 release - and could not put it down. In TypeScript he found a language that could bring all of the design patterns and practices he had learnt over the years - in a variety of languages - to JavaScript.

    Some 6 days after the 0.8.0 release, Nathan began blogging about TypeScript; covering a variety of topics, including Unit Testing, implementing an IoC Container, and Organizing your code with AMD Modules. He knew he had hit the mark when Microsoft themselves started to reference his blog in their CodePlex discussion forums

    Browse publications by this author

Latest Reviews

(7 reviews total)
Stop asking me to fill in multiple forms! You got my feedback once already.
Rychlé doručení, vynikající kvalita
very good easy experience
Book Title
Access this book, plus 7,500 other titles for FREE
Access now