Book Image

Mastering TypeScript - Second Edition

By : Nathan Rozentals
Book Image

Mastering TypeScript - Second Edition

By: Nathan Rozentals

Overview of this book

<p>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.</p> <p>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.</p>
Table of Contents (21 chapters)
Mastering TypeScript - Second Edition
Credits
About the Author
About the Reviewers
www.PacktPub.com
Customer Feedback
Preface
Free Chapter
1
TypeScript - Tools and Framework Options

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.