Appendix B. TypeScript as a language for Angular applications

You may be wondering, why not just develop in JavaScript? Why do we need to use other programming languages if JavaScript is already a language? You wouldn’t find articles about languages for developing Java or C# applications, would you?

The reason is that developing in JavaScript isn’t overly productive. Say a function expects a string value as an argument, but the developer mistakenly invokes it by passing a numeric value. With JavaScript, this error can be caught only at runtime. Java or C# compilers won’t even compile code that has mismatching types, but JavaScript is forgiving because it’s a dynamically typed language.

Although JavaScript engines do a good job of guessing the types of variables by their values, development tools have a limited ability to help you without knowing the types. In mid- and large-size applications, this JavaScript shortcoming lowers the productivity of software developers.

On larger projects, good IDE context-sensitive help and support for refactoring are important. Renaming all occurrences of a variable or function name in statically typed languages is done by IDEs in a split second, even in projects that have thousands of lines of code; but this isn’t the case in JavaScript, which doesn’t support types. IDEs can help with refactoring much better when the types of the variables are known.

To be more productive, you may consider developing in a statically typed language and then convert the code to JavaScript for deployment. Currently there are dozens of languages that compile to JavaScript (see the list of languages that compile to JS on GitHub at http://mng.bz/vjzi). The most popular are TypeScript (www.typescriptlang.org), CoffeeScript (http://coffeescript.org), and Dart (www.dartlang.org).

Why not use the Dart language?

We spent quite a bit of time working with Dart, and we like the language, but it has some drawbacks:

  • Interoperability with third-party JavaScript libraries isn’t that great.
  • Development in Dart can be done only in a specialized version of the Chrome browser (Dartium) that comes with the Dart VM. No other web browsers have it.
  • The generated JavaScript isn’t easily readable by a human.
  • The community of Dart developers is rather small.

The Angular framework is written in TypeScript, and in this appendix we’ll cover its syntax. All the code samples in this book are written in TypeScript. We’ll also show you how to turn TypeScript code into its JavaScript version so it can be executed by any web browser or a standalone JavaScript engine.

B.1. Why write Angular apps in TypeScript?

You can write applications in ES6 (and even in ES5), but we use TypeScript as a substantially more productive way for writing JavaScript. Here’s why:

  • TypeScript supports types. This allows the TypeScript compiler to help you find and fix lots of errors during development before even running the app.
  • Great IDE support is one of TypeScript’s main advantages. If you make a mistake in a function or a variable name, it’s displayed in red. If you pass the wrong number of parameters (or wrong types) to a function, the wrong ones show in red. IDEs also offer great context-sensitive help. TypeScript code can be refactored by IDEs, whereas JavaScript has to be refactored manually. If you need to explore a new library, just install its type-definitions file, and the IDE will prompt you with available APIs so you don’t need to read its documentation elsewhere.
  • Angular is bundled with type definitions files, so IDEs perform type checking while using the Angular API and offer context-sensitive help right out of the box.
  • TypeScript follows the ECMAScript 6 and 7 specifications and adds to them types, interfaces, decorators, class member variables (fields), generics, and the keywords public and private. Future releases of TypeScript will support the missing ES6 features and implement the features of ES7 (see the TypeScript “Roadmap” on GitHub at http://mng.bz/Ri29).
  • TypeScript interfaces allow you to declare custom types that will be used in your application. Interfaces help prevent compile-time errors caused by using objects of the wrong types in your application.
  • The generated JavaScript code is easy to read, and it looks like hand-written code.
  • Most of the code samples in the Angular documentation, articles, and blogs are given in TypeScript (see https://angular.io/docs).

B.2. The role of transpilers

Web browsers don’t understand any language but JavaScript. If the source code is written in TypeScript, it has to be transpiled into JavaScript before you can run it in the browser’s or a standalone JavaScript engine.

Transpiling means converting the source code of a program in one language into source code in another language. Many developers prefer to use the word compiling, so phrases like “TypeScript compiler” and “compile TypeScript into JavaScript” are also valid.

Figure B.1 shows a screenshot with TypeScript code on the left and its equivalent in an ES5 version of JavaScript generated by the TypeScript transpiler. In TypeScript, we declared a variable foo of type string, but the transpiled version doesn’t have the type information. In TypeScript, we declared a class Bar, which was transpiled in a class-like pattern in the ES5 syntax. If we had specified ES6 as a target for transpiling, the generated JavaScript code would look different.

Figure B.1. Transpiling TypeScript into ES5

A combination of Angular with statically typed TypeScript simplifies the development of medium and large web applications. Good tooling and the static type analyzer substantially decrease the number of runtime errors and will shorten the time to market. When complete, your Angular application will have lots of JavaScript code; and although developing in TypeScript will require you to write more code, you’ll reap the benefits by saving time on testing and refactoring and minimizing the number of runtime errors.

B.3. Getting started with TypeScript

Microsoft has open-sourced TypeScript, and it hosts the TypeScript repository on GitHub at http://mng.bz/Ri29. You can install the TypeScript compiler using npm or download it from www.typescriptlang.org. The TypeScript site also has a web-hosted TypeScript compiler (a playground), where you can enter TypeScript code and compile it to JavaScript interactively, as shown in figure B.2.

Figure B.2. Using the TypeScript playground

In the TypeScript playground, you enter TypeScript code on the left, and its JavaScript version is displayed on the right. Click the Run button to execute the transpiled code (open the browser’s Developer Tools to see the console output produced by your code, if any).

Interactive tools will suffice for learning the language’s syntax, but for real-world development you’ll need to use the right tooling to be productive. You may decide to use an IDE or a text editor, but having the TypeScript compiler installed locally is a must for development.

B.3.1. Installing and using the TypeScript compiler

The TypeScript compiler is itself written in TypeScript. You’ll use Node.js’s npm package manager to install the compiler. If you don’t have Node, download and install it from http://nodejs.org. Node.js comes with npm, which you’ll use to install not only the TypeScript compiler, but also many other development tools throughout the book.

To install the TypeScript compiler globally, run the following npm command in the command or terminal window:

npm install -g typescript

The -g option installs the TypeScript compiler globally on your computer, so it’s available from the command prompt for all of your projects. To develop Angular 2 applications, download the latest version of the TypeScript compiler (we use version 2.0 in this book). To check the version of your TypeScript compiler, run the following command:

tsc --version

Code written in TypeScript has to be transpiled into JavaScript so web browsers can execute it. TypeScript code is saved in files with the .ts extension. Say you wrote a script and saved it in the file main.ts. The following command will transpile main.ts into main.js.

tsc main.ts

You can also generate source map files that map the source code in TypeScript to the generated JavaScript. With source maps, you can place breakpoints in your TypeScript code while running it in the browser, even though it executes JavaScript. To compile main.ts into main.js while also generating the main.map source map file, you run the following command:

tsc --sourcemap main.ts

Figure B.3 shows a screenshot we took while debugging in Chrome Developer Tools. Note the breakpoint at line 15. You can find your TypeScript file in the Sources tab of the Developer Tools panel, place a breakpoint in the code, and watch the values of the variables on the right.

Figure B.3. Debugging TypeScript in Chrome Developer Tools

During compilation, TypeScript’s compiler removes all TypeScript types, interfaces, and keywords from the generated code to produce valid JavaScript. By providing compiler options, you can generate JavaScript compliant with ES3, ES5, or ES6 syntax. Currently ES3 is the default. Here’s how to transpile the code to ES5-compatible syntax:

tsc --t ES5 main.ts

Transpiling TypeScript in a web browser

During development, we use tsc installed locally, but transpiling can also be done either on the server during deployment or on the fly when the web browser loads your application. In this book, we use the SystemJS library, which internally uses tsc to transpile and dynamically load app modules.

Keep in mind that transpiling on the fly in the browser may introduce delays in displaying your app’s content on the user’s device. If you use SystemJS to load and transpile your code in the browser, source maps will be generated by default.

If you want to compile your code in memory without generating output .js files, run tsc with the --noEmit option. We often use this option in development mode because we just need to have executable JavaScript code in the browser’s memory.

You can start your TypeScript compiler in watch mode by providing the -w option. In this mode, whenever you modify and save your code, it’s automatically transpiled into the corresponding JavaScript files. To compile and watch all .ts files, run the following command:

tsc -w *.ts

The compiler will compile all the TypeScript files, print error messages (if any) on the console, and continue watching the files for changes. As soon as a file changes, tsc will immediately recompile it.

Note

Typically, we don’t use the IDE to compile TypeScript. We use either SystemJS with its in-browser compiler or a bundler (Webpack) that uses a special TypeScript loader for compilation. We use the TypeScript code analyzer provided by the IDEs to highlight errors, and the browser to debug TypeScript.

The TypeScript compiler allows you to preconfigure the process of compilation (specifying the source and destination directories, source map generation, and so on). The presence of the tsconfig.json configuration file in the project directory means you can enter tsc on the command line, and the compiler will read all the options from tsconfig.json. A sample tsconfig.json file is shown here.

Listing B.1. tsconfig.json
   "compilerOptions": {
      "target": "es5",
      "module": "commonjs",
      "emitDecoratorMetadata": true,
      "experimentalDecorators": true,
      "rootDir": ".",
      "outDir": "./js"
   }
}

This config file instructs tsc to transpile the code into ES5 syntax. The generated JavaScript files will be located in the js directory. The tsconfig.json file may include the files section that lists the files that have to be compiled by TypeScript. Listing B.1 doesn’t include this list because you use the rootDir option to request the compilation of all files starting from the root directory of the project.

If you want to exclude some of your project’s files from compilation, add the exclude property to tsconfig.json. This is how you can exclude the entire content of the node_modules directory:

"exclude": [
    "node_modules"
  ]

You can read more about configuring the compilation process and the TypeScript compiler’s options at in the TypeScript documentation (http://mng.bz/rf14).

Note

Most of the Angular examples in this book use annotations (a.k.a. decorators) with classes or class members (such as @Component and @Input). Annotations are a way to add metadata to the annotated classes or their members. See the “What’s metadata?” sidebar in section 2.1.1 for more details.

B.4. TypeScript as a superset of JavaScript

TypeScript fully supports ES5 and most of the ES6 syntax. Just change the name extension of a file with JavaScript code from .js to .ts, and it’ll become valid TypeScript code. The only two exceptions we’ve seen so far are handling optional function parameters and assigning a value to an object literal.

In JavaScript, even if a function is declared with two parameters, you can invoke it by providing only one, whereas in TypeScript you need to append a question mark to the parameter name to make it optional. In JavaScript, you can initialize a variable with an empty object literal and immediately attach a property using dot notation, whereas in TypeScript you’d need to use square brackets.

But these differences are minor. What’s more important is that because it’s a superset of JavaScript, TypeScript adds a number of useful features to JavaScript. We’ll review them next.

Tip

If you’re in the middle of converting a JavaScript project to TypeScript, you can use the tsc compiler’s --allowJs option. The TypeScript compiler will check the input .js files for syntax errors and emit valid output based on the --target and --module options of tsc. The output can be combined with other .ts files as well. Source maps are still generated for .js files, just as with .ts files.

B.5. Optional types

You can declare variables and provide types for all or some of them. The following two lines are valid TypeScript syntax:

var name1 = 'John Smith';

var name2: string = 'John Smith';

If you use types, TypeScript’s transpiler can detect mismatched types during development, and IDEs will offer code completion and refactoring support. This will increase your productivity on any decent-sized project. Even if you don’t use types in declarations, TypeScript will guess the type based on the assigned value and will still do type checking afterward. This is called type inference.

The following fragment of TypeScript code shows that you can’t assign a numeric value to a name1 variable that was meant to be a string, even though it was initially declared without a type (JavaScript syntax). After initializing this variable with a string value, the inferred typing won’t let you assign the numeric value to name1. The same rule applies to the variable name2, which is declared with an explicit type:

In TypeScript, you can declare typed variables, function parameters, and return values. There are four keywords for declaring basic types: number, boolean, string, and void. The latter indicates the absence of a return value in a function declaration. A variable can have a value of type null or undefined, similar to JavaScript.

Here are some examples of variables declared with explicit types:

var salary: number;
var name: string = "Alex";
var isValid: boolean;
var customerName: string = null;

All of these types are subtypes of the any type. If you don’t specify a type while declaring a variable or a function argument, TypeScript’s compiler will assume that it has the type any, which will allow you to assign any value to this variable or function argument. You may as well explicitly declare a variable, specifying any as its type. In this case, inferred typing isn’t applied. Both of these declarations are valid:

var name2: any = 'John Smith';
name2 = 123;

If variables are declared with explicit types, the compiler will check their values to ensure that they match the declarations. TypeScript includes other types that are used in interactions with the web browser, such as HTMLElement and Document.

If you define a class or an interface, it can be used as a custom type in variable declarations. We’ll introduce classes and interfaces later, but first let’s get familiar with TypeScript functions, which are the most-used constructs in JavaScript.

B.5.1. Functions

TypeScript functions (and function expressions) are similar to JavaScript functions, but you can explicitly declare parameter types and return values. Let’s write a JavaScript function that calculates tax. It’ll have three parameters and will calculate tax based on the state, income, and number of dependents. For each dependent, the person is entitled to a $500 or $300 tax deduction, depending on the state the person lives in.

Listing B.2. Calculating tax in JavaScript
function calcTax(state, income, dependents) {
    if (state == 'NY') {
        return income * 0.06 - dependents * 500;
    } else if (state == 'NJ') {
        return income * 0.05 - dependents * 300;
    }
}

Say a person with an income of $50,000 lives in the state of New Jersey and has two dependents. Let’s invoke calcTax():

var tax = calcTax('NJ', 50000, 2);

The tax variable gets the value of 1,900, which is correct. Even though calcTax() doesn’t declare any types for the function parameters, you can guess them based on the parameter names.

Now let’s invoke it the wrong way, passing a string value for a number of dependents:

var tax = calcTax('NJ', 50000, 'two');

You won’t know there’s a problem until you invoke this function. The tax variable will have a NaN value (not a number). A bug sneaked in just because you didn’t have a chance to explicitly specify the types of the parameters. Let’s rewrite this function in TypeScript, declaring types for parameters and the return value.

Listing B.3. Calculating tax in TypeScript
function calcTax(state: string, income: number, dependents: number): number{

    if (state == 'NY'){
        return income*0.06 - dependents*500;
    } else if (state=='NJ'){
        return income*0.05 - dependents*300;
    }
}

Now there’s no way to make the same mistake and pass a string value for the number of dependents:

var tax: number = calcTax('NJ', 50000, 'two');

The TypeScript compiler will display an error saying, “Argument of type ‘string’ is not assignable to parameter of type ‘number’.” Moreover, the return value of the function is declared as number, which stops you from making another mistake and assigning the result of the tax calculations to a non-numeric variable:

var tax: string = calcTax('NJ', 50000, 'two');

The compiler will catch this, producing the error “The type ‘number’ is not assignable to type ‘string’: var tax: string.” This kind of type-checking during compilation can save you a lot of time on any project.

B.5.2. Default parameters

While declaring a function, you can specify default parameter values. The only limitation is that parameters with default values can’t be followed by required parameters. In listing B.3, to provide NY as a default value for the state parameter, you can’t declare it as follows:

function calcTax(state: string = 'NY', income: number, dependents: number):
 number{
    // the code goes here
}

You need to change the order of the parameters to ensure that there are no required parameters after the default one:

function calcTax(income: number, dependents: number, state: string = 'NY'):
 number{
    // the code goes here
}

There’s no need to change even one line of code in the body of calcTax(). You now have the freedom to invoke it with either two or three parameters:

var tax: number = calcTax(50000, 2);
// or
var tax: number = calcTax(50000, 2, 'NY');

The results of both invocations will be the same.

B.5.3. Optional parameters

In TypeScript, you can easily mark function parameters as optional by appending a question mark to the parameter name. The only restriction is that optional parameters must come last in the function declaration. When you write code for functions with optional parameters, you need to provide application logic that handles the cases when the optional parameters aren’t provided.

Let’s modify the tax-calculation function: if no dependents are specified, it won’t apply any deduction to the calculated tax.

Listing B.4. Calculating tax in TypeScript, modified

Note the question mark in dependents?: number. Now the function checks whether the value for dependents was provided. If it wasn’t, you assign 0 to the deduction variable; otherwise, you deduct 500 for each dependent.

Running listing B.4 will produce the following output:

Your tax is 1000
Your tax is 3000

B.5.4. Arrow-function expressions

TypeScript supports simplified syntax for using anonymous functions in expressions. There’s no need to use the function keyword, and the fat-arrow symbol (=>) is used to separate function parameters from the body. TypeScript supports the ES6 syntax for arrow functions (appendix A has more details on arrow functions). In some other programming languages, arrow functions are known as lambda expressions.

Let’s look at the simplest example of an arrow function with a single-line body:

var getName = () => 'John Smith';
console.log(getName());

The empty parentheses denote that the preceding arrow function has no parameters. A single-line arrow expression doesn’t need curly braces or an explicit return statement, and the preceding code fragment will print “John Smith” on the console. If you try that code in TypeScript’s playground, it’ll convert it to the following ES5 code:

var getName = function () { return 'John Smith'; };
console.log(getName());

If the body of your arrow function consists of multiple lines, you’ll have to enclose it in curly braces and use the return statement. The following code snippet converts a hardcoded string value to uppercase and prints “PETER LUGER” on the console:

var getNameUpper = () => {
    var name = 'Peter Luger'.toUpperCase();
    return name;
}
console.log(getNameUpper());

In addition to providing a shorter syntax, arrow-function expressions remove the infamous confusion with the this keyword. In JavaScript, if you use the this keyword in a function, it may not point at the object where the function is being invoked. That can result in runtime bugs and require additional time for debugging. Let’s look at an example.

Listing B.5 has two functions: StockQuoteGeneratorArrow() and StockQuote-GeneratorAnonymous(). Each second, both of these functions invoke Math.random() to generate a random price for the stock symbol provided as a parameter. Internally, StockQuoteGeneratorArrow() uses the arrow-function syntax, providing the argument for setInterval(), whereas StockQuoteGeneratorAnonymous() uses the anonymous function.

Listing B.5. Using an arrow-function expression

In both cases, you assign the stock symbol (“IBM”) to the symbol variable on the this object. But with the arrow function, the reference to the instance of the StockQuoteGeneratorArrow() constructor function is automatically saved in a separate variable; when you refer to this.symbol from the arrow function, it properly finds it and uses “IBM” in the console output.

But when the anonymous function is invoked in the browser, this points at the global Window object, which doesn’t have the symbol property. Running this code in the web browser will print something like this every second:

StockQuoteGeneratorArrow. The price quote for IBM is 0.2998261866159737
StockQuoteGeneratorAnonymous.The price quote for undefined is
 0.9333276399411261

As you see, when you use the arrow function, it recognizes IBM as the stock symbol, but it’s undefined in the anonymous function.

Note

TypeScript replaces the this in the arrow function expression with a reference to the outer scope’s this by passing in the reference. This is why the code in the arrow in StockQuoteGeneratorArrow() properly sees this.symbol from the outer scope.

Our next topic is TypeScript classes, but let’s take a brief pause and summarize what we’ve covered so far:

  • Typescript code is compiled into JavaScript using the tsc compiler.
  • TypeScript allows you to declare the types of variables, function parameters, and return values.
  • Functions can have parameters with default values and optional parameters.
  • Arrow-function expressions offer a shorter syntax for declaring anonymous functions.
  • Arrow-function expressions eliminate the uncertainty in using the this object reference.

Function overloading

JavaScript doesn’t support function overloading, so having several functions with the same name but different lists of arguments isn’t possible. The TypeScript creators introduced function overloading, but because the code has to be transpiled into a single JavaScript function, the syntax for overloading isn’t elegant.

You can declare several signatures of a function with one and only one body, where you need to check the number and types of the arguments and execute the appropriate portion of the code:

function attr(name: string): string;
function attr(name: string, value: string): void;
function attr(map: any): void;
function attr(nameOrMap: any, value?: string): any {
  if (nameOrMap && typeof nameOrMap === "string") {
      // handle string case
  } else {
      // handle map case
  }

  // handle value here
}

B.6. Classes

If you have Java or C# experience, you’ll be familiar with the concepts of classes and inheritance in their classical form. In those languages, the definition of a class is loaded in memory as a separate entity (like a blueprint) and is shared by all instances of this class. If a class is inherited from another one, the object is instantiated using the combined blueprint of both classes.

TypeScript is a superset of JavaScript, which only supports prototypal inheritance, where you can create an inheritance hierarchy by attaching one object to the prototype property of another. In this case, an inheritance (or rather, a linkage) of objects is created dynamically.

In TypeScript, the class keyword is syntactic sugar to simplify coding. In the end, your classes will be transpiled into JavaScript objects with prototypal inheritance. In JavaScript, you can declare a constructor function and instantiate it with the new keyword. In TypeScript, you can also declare a class and instantiate it with the new operator.

A class can include a constructor, fields (a.k.a. properties), and methods. Properties and methods declared are often referred as class members. We’ll illustrate the syntax of TypeScript classes by showing you a series of code samples and comparing them with the equivalent ES5 syntax.

Let’s create a simple Person class that contains four properties to store the first and last name, age, and Social Security number (a unique identifier assigned to every legal resident of the United States). At left in figure B.4 you can see the TypeScript code that declares and instantiates the Person class; on the right is a JavaScript closure generated by the tsc compiler. By creating a closure for the Person function, the TypeScript compiler enables the mechanism for exposing and hiding the elements of the Person object.

Figure B.4. Transpiling a TypeScript class into a JavaScript closure

TypeScript also supports class constructors that allow you to initialize object variables while instantiating the object. A class constructor is invoked only once during object creation. The left side of figure B.5 shows the next version of the Person class, which uses the constructor keyword that initializes the fields of the class with the values given to the constructor. The generated ES5 version is shown on the right.

Figure B.5. Transpiling a TypeScript class with constructor

Some JavaScript developers may see little value in using classes, because they can easily program the same functionality using constructor functions and closures. But people who are just starting with JavaScript will find the syntax of classes easier to read and write, compared to constructor functions and closures.

B.6.1. Access modifiers

JavaScript doesn’t have a way to declare a variable or a method as private (hidden from external code). To hide a property (or a method) in an object, you need to create a closure that neither attaches this property to the this variable nor returns it in the closure’s return statement.

TypeScript provides public, protected, and private keywords to help you control access to the object’s members during the development phase. By default, all class members have public access, and they’re visible from outside the class. If a member is declared with the protected modifier, it’s visible in the class and its subclasses. Class members declared as private are visible only in the class.

Let’s use the private keyword to hide the value of the ssn property so it can’t be directly accessed from outside of the Person object. We’ll show you two versions of declaring a class with properties that use access modifiers. The longer version of the class looks like this.

Listing B.6. Using a private property
class Person {
    public firstName: string;
    public lastName: string;
    public age: number;
    private _ssn: string;

    constructor(firstName:string, lastName: string, age: number, ssn: string) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        this._ssn = ssn;
    }
}

var p = new Person("John", "Smith", 29, "123-90-4567");
console.log("Last name: " + p.lastName + " SSN: " + p._ssn);

Note that the name of the private variable starts with an underscore: _ssn. This is just a naming convention for private properties.

The last line of listing B.6 attempts to access the _ssn private property from outside, so the TypeScript code analyzer will give you a compilation error: “Property ‘ssn’ is private and is only accessible in class ‘Person’”. But unless you use the --noEmitOnError compiler option, the erroneous code will still transpile into JavaScript:

var Person = (function () {
    function Person(firstName, lastName, age, _ssn) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        this._ssn = _ssn;
    }
    return Person;
})();

var p = new Person("John", "Smith", 29, "123-90-4567");
console.log("Last name: " + p.lastName + " SSN: " + p._ssn);

The keyword private only makes it private in the TypeScript code. IDEs won’t show private members in the context-sensitive help when you try to access properties of an object from outside, but the production JavaScript code will treat all properties and methods of the class as public anyway.

TypeScript allows you to provide access modifiers with the constructor’s arguments, as shown in the following short version of the Person class.

Listing B.7. Using access modifiers
class Person {

    constructor(public firstName: string,
        public lastName: string, public age: number,  private _ssn: string) {
    }
}

var p = new Person("John", "Smith", 29, "123-90-4567");

When you use a constructor with access modifiers, the TypeScript compiler takes it as an instruction to create and retain the class properties matching the constructor’s arguments. You don’t need to explicitly declare and initialize them. Both the short and long versions of the Person class generate the same JavaScript.

B.6.2. Methods

When a function is declared in a class, it’s called a method. In JavaScript, you need to declare methods on the prototype of an object; but with a class you declare a method by specifying a name followed by parentheses and curly braces, as you would in other object-oriented languages.

The next code snippet shows how you can declare and use a MyClass class with a doSomething() method that has one argument and no return value.

Listing B.8. Creating a method
class MyClass{

   doSomething(howManyTimes: number): void{
      // do something here
   }
}

var mc = new MyClass();
mc.doSomething(5);

Static and instance members

The code of listing B.8, as well as the class shown in figure B.4, creates an instance of the class first and then accesses its members using a reference variable that points at this instance:

mc.doSomething(5);

If a class property or method were declared with the keyword static, its values would be shared between all instances of the class, and you wouldn’t need to create an instance to access static members. Instead of using a reference variable (such as mc), you’d use the name of the class:

class MyClass{

   static doSomething(howManyTimes: number): void{
      // do something here
   }
}

MyClass.doSometing(5);

If you instantiate a class and need to invoke a class method from within another method declared in the same class, you must use the this keyword (for example, this.doSomething(5)). In other programming languages, using this in the class code is optional, but the TypeScript compiler will complain that it can’t find the method if this isn’t explicitly used.

Let’s add public setter and getter methods to the Person class to set and get the value of _ssn.

Listing B.9. Adding a setter and a getter

In lisitng B.9, the getter and setter don’t contain any application logic; but in real-world applications, these methods would perform validation. For example, the code in the getter and setter may check whether the caller is authorized to get or set the value of _ssn.

Note

Getters and setters are supported in JavaScript as well, starting with the ES5 specification.

Note that in the methods, you use the this keyword to access the property of the object. It’s mandatory in TypeScript.

B.6.3. Inheritance

JavaScript supports prototypal object-based inheritance, where one object can use another object as a prototype. TypeScript has the keyword extends for inheritance of classes, like ES6 and other object-oriented languages. But during transpiling to JavaScript, the generated code uses the syntax of the prototypal inheritance.

Figure B.6 shows how to create an Employee class (line 9) that extends the class Person (shown in a screenshot from the TypeScript playground). On the right, you can see the transpiled JavaScript version, which uses prototypal inheritance. The TypeScript version of the code is more concise and easier to read.

Figure B.6. Class inheritance in TypeScript

Let’s add a constructor and a department property to the Employee class.

Listing B.10. Using inheritance

If you invoke a method declared in a superclass on the object of the subclass type, you can use the name of this method as if it were declared in the subclass. But sometimes you want to specifically call the method of the superclass, and this is when you should use the super keyword.

The super keyword can be used two ways. In the constructor of a derived class, you invoke it as a method. You can also use the super keyword to specifically call a method of the superclass. It’s typically used with method overriding. For example, if both a superclass and its descendant have a doSomething() method, the descendant can reuse the functionality programmed in the superclass and add other functionality as well:

doSomething(){
    super.doSomething();

    // Add more functionality here
}

You can read more about the super keyword in section A.7.4. We’re halfway through this appendix, so let’s take a breather and review what you’ve learned so far:

  • Even though you can write Angular applications using JavaScript’s ES5 or ES6 syntax, using TypeScript has benefits during the development stage of your project.
  • TypeScript allows you to declare the types of the primitive variables as well as to develop custom types. Transpilers erase the information about types, so your applications can be deployed in any browser that supports the syntax of ECMAScript 3, 5, or 6.
  • The TypeScript compiler turns .ts files into their .js counterparts. You can start the compiler in watch mode so this transformation will be initiated on any change in any .ts file.
  • TypeScript classes make the code more declarative. The concept of classes and inheritance is well known to developers who use other object-oriented languages.
  • Access modifiers help control access to class members during development, but they aren’t as strict as they are in languages such as Java and C#.

We’ll continue introducing more TypeScript syntax constructs starting in the next section; but if you’re eager to see how TypeScript and Angular work together, feel free to jump to section B.9.

B.7. Generics

TypeScript supports parameterized types, also known as generics, which can be used in a variety of scenarios. For example, you can create a function that can take values of any type, but during its invocation in a particular context you can explicitly specify a concrete type.

Take another example: an array can hold objects of any type, but you can specify which particular object types (for example, instances of Person) are allowed in an array. If you (or someone else) were to try to add an object of a different type, the TypeScript compiler would generate an error.

The following code snippet declares a Person class, creates two instances of it, and stores them in the workers array declared with the generic type. Generic types are denoted by placing them in the angle brackets (for example, <Person>).

Listing B.11. Using a generic type
class Person {
    name: string;
}

class Employee extends Person{
    department: number;
}

class Animal {
    breed: string;
}

var workers: Array<Person> = [];

workers[0] = new Person();
workers[1] = new Employee();
workers[2] = new Animal();  // compile-time error

In this code snippet, you declare the Person, Employee, and Animal classes and a workers array with the generic type <Person>. By doing this, you announce your plans to store only instances of the class Person or its descendants. An attempt to store an instance of an Animal in the same array will result in a compile-time error.

If you work in an organization that allows animal workers (such as police dogs), you can change the declaration of workers as follows:

var workers: Array<any> = [];
Note

You’ll see another example of using generics in section B.8. There you’ll declare a workers array of the interface type.

Can you use generic types with any object or a function? No. The creator of the object or function has to allow this feature. If you open TypeScript’s type definition file (lib.d.ts) on GitHub at http://mng.bz/I3V7 and search for “interface Array,” you’ll see the declaration of the Array, as shown in figure B.7. (Type definition files are explained in section B.10.)

Figure B.7. The fragment of lib.d.ts describing the Array API

The <T> in line 1008 means TypeScript allows you to declare a type parameter with Array and the compiler will check for the specific type provided in your program. Listing B.11 specifies this generic <T> parameter as <Person>. But because generics aren’t supported in ES6, you won’t see them in the code generated by the transpiler. It’s just an additional safety net for developers at compile time.

You can see another T in line 1022 in figure B.7. When generic types are specified with function arguments, no angle brackets are needed. But there’s no actual T type in TypeScript. The T here means the push method lets you push objects of a specific type into an array, as in the following example:

workers.push(new Person());

In this section, we’ve illustrated just one use case for working with generic types with an array that already supports generics. You can create your own classes or functions that support generics as well. If somewhere in the code you try to invoke the function saySomething() and provide a wrong argument type, the TypeScript compiler will give you an error:

The generated JavaScript won’t include any generic information, and the preceding code snippet will be transpiled into the following code:

function saySomething(data) {
}
saySomething("Hello");
saySomething(123);

If you’d like to learn about generics in depth, refer to the “Generics” section in the TypeScript Handbook (http://mng.bz/447K).

B.8. Interfaces

JavaScript doesn’t support the concept of interfaces, which in other object-oriented languages are used to introduce a code contract that an API has to abide by. An example of a contact can be class X declaring that it implements interface Y. If class X won’t include an implementation of the methods declared in interface Y, it’s considered a violation of the contract and won’t compile.

TypeScript includes the keywords interface and implements to support interfaces, but interfaces aren’t transpiled into JavaScript code. They just help you avoid using the wrong types during development.

In TypeScript, there are two patterns for using interfaces:

  • Declare an interface that defines a custom type containing a number of properties. Then declare a method that has an argument of such a type. The compiler will check when this method is invoked that the object given as an argument includes all the properties declared in the interface.
  • Declare an interface that includes abstract (non-implemented) methods. When a class declares that it implements this interface, the class must provide an implementation for all the abstract methods.

Let’s consider these two patterns by example.

B.8.1. Declaring custom types with interfaces

When you use JavaScript frameworks, you may run into an API that requires some sort of configuration object as a function parameter. To figure out which properties must be provided in this configuration object, you need to either open the documentation for the API or read the source code of the framework. In TypeScript, you can declare an interface that includes all the properties, and their types, that must be present in a configuration object.

Let’s see how this can be done in the Person class, which contains a constructor with four arguments: firstName, lastName, age, and ssn. This time, you’ll declare an IPerson interface that contains the four members, and you’ll modify the constructor of the Person class to use an object of this custom type as an argument.

Listing B.12. Declaring an interface

TypeScript has a structural type system, which means if two different types include the same members, the types are considered compatible. Having the same members means the members have the same names and types. In listing B.12, even if you didn’t specify the type of the aPerson variable, it still would be considered compatible with IPerson and could be used as a constructor argument while instantiating the Person object.

If you change the name or type of one of the members of IPerson, the TypeScript compiler will report an error. On the other hand, if you try to instantiate a Person that contains an object with all the required members of IPerson and some other members, it won’t raise a red flag. You could use the following object as a constructor argument for a Person:

var anEmployee: IPerson = {
    firstName: "John",
    lastName: "Smith",
    age: 29,
    department: "HR"
}

The department member wasn’t defined in the IPerson interface, but as long as the object has all other members listed in the interface, the contract terms are fulfilled.

The IPerson interface didn’t define any methods, but TypeScript interfaces can include method signatures without implementations.

B.8.2. Using the implements keyword

The implements keyword can be used with a class declaration to announce that the class will implement a particular interface. Say you have an IPayable interface that’s declared as follows:

interface IPayable{
  increase_cap:number;

  increasePay(percent: number): boolean
}

Now the Employee class can declare that it implements IPayable:

class Employee implements IPayable{

    // The implementation goes here
}

Before going into implementation details, let’s answer this question: “Why not just write all required code in the class rather than separating a portion of the code into an interface?” Let’s say you need to write an application that lets you increase salaries for the employees of your organization. You can create an Employee class (that extends Person) and include the increaseSalary() method there. Then the business analysts may ask you to add the ability to increase pay to contractors who work for your firm. But contractors are represented by their company names and IDs; they have no notion of salary and are paid on an hourly basis.

You can create another class, Contractor (not inherited from Person), that includes some properties and an increaseHourlyRate() method. Now you have two different APIs: one for increasing the salary of employees, and another for increasing the pay for contractors. A better solution is to create a common IPayable interface and have Employee and Contractor classes provide different implementations of IPayable for these classes, as illustrated next.

Listing B.13. Using multiple interface implementations

Running listing B.13 produces the following output on the browser’s console:

Increasing salary by 30
Sorry, the increase cap for contractors is 20
Why declare classes with the implements keyword?

Listing B.13 illustrates the structural subtyping of TypeScript. If you remove implements Payable from the declaration of either Employee or Contractor, the code will still work, and the compiler won’t complain about lines that add these objects to the workers array. The compiler is smart enough to see that even if the class doesn’t explicitly declare implements IPayable, it implements increasePay() properly.

But if you remove implements IPayable and try to change the signature of the increasePay() method from any of the classes, you won’t be able to place such an object into the workers array, because that object would no longer be of the IPayable type. Also, without the implements keyword, IDE support (such as for refactoring) will be broken.

B.8.3. Using callable interfaces

TypeScript has an interesting feature known as a callable interface that contains a bare function signature (a signature without a function name). The following example shows a bare function signature that takes one parameter of type number and returns a boolean:

(percent: number): boolean;

The bare function signature indicates that the instance of the interface is callable. In listing B.14, we’ll show you a different version of declaring IPayable, which contains a bare function signature. For brevity, we’ve removed the inheritance in this example. You’ll declare separate functions that implement rules for pay increase for employees and contractors. These functions will be passed as arguments and invoked by the constructor of the Person class.

Listing B.14. Callable interface with a bare function

Running listing B.14 will generate the following output on the browser’s console:

Increasing salary by 30
Sorry, the increase cap for contractors is 20

Interfaces support inheritance with the extends keyword. If a class implements interface A that’s extended from interface B, the class must implement all members from A and B.

Treating classes as interfaces

In TypeScript, you can think of any class as an interface. If you declare class A {} and class B {}, it’s perfectly legal to write class A implements B {}. You can see an example of this syntax in section 4.4.

TypeScript interfaces don’t generate any output when transpiled to JavaScript, and if you place just an interface declaration into a separate file (such as ipayable.ts) and compile it with tsc, it will generate an empty ipayable.js file. If you load the code that imports the interface from a file (such as from ipayable.js) using System-JS, you’ll get an error because you can’t import an empty file. You need to let SystemJS know that it has to treat IPayable as a module and register it in the global System registry. This can be done while configuring SystemJS by using the meta annotation, as shown here:

System.config({
  transpiler: 'typescript',
  typescriptOptions: {emitDecoratorMetadata: true},
  packages: {app: {defaultExtension: 'ts'}},
  meta: {
    'app/ipayable.ts': {
      format: 'es6'
    }
}

In addition to providing a way to create custom types and minimize the number of type-related errors, the mechanism of interfaces greatly simplifies the implementation of the Dependency Injection design pattern explained in chapter 4.

This concludes our brief introduction to interfaces. You can find more details in the “Interfaces” section of the TypeScript Handbook (http://mng.bz/spm7).

Note

The TypeDoc utility is a convenient tool for generating program documentation based on the comments in your TypeScript code. You can get it at www.npmjs.com/package/typedoc.

We’re almost done with this TypeScript syntax overview. It’s time to bring TypeScript and Angular together.

B.9. Adding class metadata with annotations

There are different definitions of the term metadata. The popular definition is that metadata is data about data. We think of metadata as data that describes code. TypeScript decorators provide a way to add metadata to your code. In particular, to turn a TypeScript class into an Angular component, you can annotate it with metadata. Annotations start with an @ sign.

To turn a TypeScript class into an Angular UI component, you need to decorate it with the @Component annotation. Angular will internally parse your annotations and generate code that adds the requested behavior to the TypeScript class:

@Component({
  // Include here the selector (the name) to identify
  // the component in the HTML document.

  // Provide the template property with the
  // HTML fragment to render the component.
  // Component styling also goes here.
})
class HelloWorldComponent {
  // The code implementing the component's
  // application logic goes here.
}

When you use annotations, there should be an annotation processor that can parse the annotation content and turn it into code that the runtime (the browser’s JavaScript engine) understands. In the context of this book, Angular’s compiler ngc performs the duties of the annotation processor.

To use the annotations supported by Angular, you need to import their implementation in your application code. For example, you need to import the @Component annotation from the Angular module:

import { Component } from 'angular2/core';

Although the implementation of these annotations is done in Angular, you may want a standardized mechanism for creating your own annotations. This is what TypeScript decorators are for. Think of it this way: Angular offers you its annotations that let you decorate your code, but TypeScript allows you to create your own annotations with the support of decorators.

B.10. Type-definition files

For several years, a large repository of TypeScript definition files called DefinitelyTyped was the only source of TypeScript type definitions for the new ECMAScript API and for hundreds of popular frameworks and libraries written in JavaScript. The purpose of these files is to let the TypeScript compiler know the types expected by the APIs of these libraries. Although the http://definitelytyped.org repository still exists, npmjs.org became a new repository for type-definition files, and we use it in all the code samples in this book.

The suffix of any definition filename is d.ts, and you can find the definition files in Angular modules in the subfolders of the node_modules/@angular folder after running npm install as explained in chapter 2. All required *.d.ts files are bundled with Angular npm packages, and there’s no need to install them separately. The presence of the definition files in your project will allow the TypeScript compiler to ensure that your code uses the correct types while invoking the Angular API.

For example, Angular applications are launched by invoking the bootstrapModule() method, giving it the root module for your application as an argument. The application_ref.d.ts file includes the following definition for this function:

bootstrapModule<M>(moduleType: ConcreteType<M>,
compilerOptions?: CompilerOptions | CompilerOptions[]):
Promise<NgModuleRef<M>>;

By reading this definition, you (and the tsc compiler) know that this function can be invoked with one mandatory parameter of type ConcreteType and an optional array of compiler options. If the application_ref.d.ts file wasn’t a part of your project, TypeScript’s compiler would let you invoke the bootstrapModule function with a wrong parameter type, or without any parameters at all, which would result in a runtime error. But application_ref.d.ts is present, so TypeScript would generate a compile-time error reading “Supplied parameters do not match any signature of call target.” Type-definition files also allow IDEs to show context-sensitive help when you’re writing code that invokes Angular functions or assigns values to objects’ properties.

B.10.1. Installing type-definition files

To install TypeScript type-definition files for a library or framework written in JavaScript, developers have used type-definition managers: tsd and Typings. The former was deprecated because it only let us get *.d.ts files from definitelytyped.org. Prior to the release of TypeScript 2.0, we used Typings (https://github.com/typings/typings), which allowed us to bring in type definitions from an arbitrary repository.

With the release of TypeScript 2.0, there’s no need to use type-definition managers for npm-based projects. Now the npmjs.org npm repository includes a @types organization that stores type definitions for the popular JavaScript libraries. All libraries from definitelytyped.org are published there.

Let’s say you need to install a type-definitios file for jQuery. Running the following command will install the type definitions in the node_modules/@types directory and save this dependency in the package.json file of your project:

npm install @types/jquery --save-dev

In this book, you’ll install type definitions using similar commands in many sample projects. For example, ES6 has introduced the find() method for arrays, but if your TypeScript project is configured to use ES5 as a target for compilation, your IDE will highlight the find() method in red because ES5 doesn’t support it. Installing the type-definition file for es6-shim will get rid of the redness in your IDE:

npm i @types/es6-shim --save-dev
What if tsc can’t find the type-definition file?

At the time of writing (TypeScript 2.0), there’s a chance tsc won’t find type-definition files located in the node_modules/@types directory. If you run into this issue, add the required files to the types section of tsconfig.json. Here’s an example:

"compilerOptions": {
   ...
  "types":["es6-shim", "jasmine"],
}
Module resolution and the reference tag

Unless you use CommonJS modules, you’ll need to explicitly add a reference to your TypeScript code, pointing at the required type definitions, like this:

/// <reference types="typings/jquery.d.ts" />

You use CommonJS modules as a tsc option, and each project includes the following option in the tsconfig.json file:

"module": "commonjs"

When tsc sees an import statement referring to a module, it automatically tries to find the <module-name>d.ts file in the node_modules directory. If that’s not found, it goes up one level and repeats the process. You can read more about this in the “Typings for npm Modules” section in the TypeScript Handbook (http://mng.bz/ih4z). In upcoming releases of tsc, the same strategy will be implemented for AMD module resolution.

Angular includes all required definition files, and you don’t need to use a type-definition manager unless your application uses other third-party JavaScript libraries. In this case, you need to install their definition files manually to get context-sensitive help in your IDE.

Angular uses the ES6 syntax in its d.ts files, and for most modules you can use the following import syntax: import {Component} from 'angular2/core';. The definition of the Component class will be found. You’ll be importing all other Angular modules and components.

Controlling code style with TSLint

TSLint is a tool you can use to ensure that your programs are written according to specified rules and coding styles. You can configure TSLint to check that the TypeScript code in your project is properly aligned and indented, that the names of all interfaces start with a capital I, that class names use CamelCase notation, and so on.

You can install TSLint globally using the following command:

npm install tslint -g

To install the TSLint node module in your project directory, run the following command:

npm install tslint

The rules you want to apply to your code are specified in a tslint.json configuration file. A sample rules file comes with TSLint. The file’s name is sample.tslint.json, and it’s located in the docs directory. You can turn specific rules on or off as needed.

For details on using TSLint, visit www.npmjs.com/package/tslint. Your IDE may support linting with TSLint out of the box.

IDEs

We want to make the content of this book IDE-agnostic, and we don’t include instructions specific to any IDE. But several IDEs support TypeScript. The most popular are WebStorm, Visual Studio Code, Sublime Text, and Atom. All of these IDEs and editors work under Windows, Mac OS, and Linux. If you develop your TypeScript/Angular applications on a Windows computer, you can use Visual Studio 2015.

B.11. An overview of the TypeScript/Angular development process

The process of developing and deploying TypeScript/Angular applications consists of multiple steps, which should be automated as much as possible. There are multiple ways to do that. The following is a sample list of steps that could be performed to create an Angular application:

  1. Create a directory for your project.
  2. Create a package.json file that lists all of your application dependencies, such as Angular packages, Jasmine testing frameworks, and so on.
  3. Install all the packages and libraries listed in package.json using the command npm install.
  4. Write the application code.
  5. Load your application into the browser with the help of the SystemJS loader, which not only loads, but also transpiles TypeScript into JavaScript in the browser.
  6. Minimize and bundle your code and resources with the help of Webpack and its plugins.
  7. Copy all the files into the distribution directory using the npm scripts.

Chapter 2 explains how to start a new Angular project and work with the npm package manager and the SystemJS module loader.

Note

Angular-CLI is a command-line utility that can scaffold your project, generate components and services, and prepare builds. We introduce Angular CLI in chapter 10.

Note

We haven’t mentioned the subject of error handling in this appendix, but because TypeScript is a superset of JavaScript, error handling is done the same way as in JavaScript. You can read about different types of errors in the JavaScript Reference article on the Error constructor, on the Mozilla Developer Network (http://mng.bz/FwfO).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.114.132