CHAPTER 2

image

The New Syntax

In this chapter, you’re going to look at the TypeScript additions to the JavaScript language. You’ll learn about its strong typing features first and then move on to its object-oriented specific features. In particular, this chapter covers the following:

  • Static typing
  • Classes
  • Interfaces
  • Modules

TypeScript is a design-time-only language—that is, it exists only prior to compilation (into JavaScript) in your IDE—so I’ll try to reveal as much of the tooling that’s relevant to each feature as you go along as well.

Most of you reading this will be new to TypeScript, but not necessarily to JavaScript, so I’ll assume you know at least some basic JavaScript as well as C# or Visual Basic. I’ll drop into a discussion of JavaScript features only when relevant.

image Tip  If you’re completely new to JavaScript, have a look at www.w3schools.com/js/default.asp for a quick guide and then come back. I’ll wait.

The Story So Far

In Chapter 1, you saw how to create an HTML Application with TypeScript project and wrote your first TypeScript code:

/// <reference path="libjquery.1.8.d.ts" />
 
module TypeScript.Revealed {
    export class Chapter1 {
        static DisplayDate(): void {
            var currentDate: Date = new Date();
            $("#txtDemo").text(currentDate.toUTCString());
        }
    }
}
 
$("#btnGo").click(TypeScript.Revealed.Chapter1.DisplayDate);

You saw that as you typed, the TypeScript Language Service (TLS) that is part of the Visual Studio extension prompted you with type information and alerted you to any errors it detected in your code. You also learned how declaration files are used to provide TLS with IntelliSense and type-safe information for third-party libraries such as jQuery. Finally, you saw that when the project was compiled, the TypeScript file was compiled into native ECMAScript 3–compliant JavaScript for use in the HTML page you wrote to accompany it, as shown in Figure 2-1.

9781430257257_Fig02-01.jpg

Figure 2-1 .  The TLS displays the JavaScript your TypeScript will be compiled into

You should have no problem figuring out how the code translates to the output, but let’s point out a few features that you’ll investigate further in this chapter:

  • Line 1 references the jQuery declaration file so the TLS extension can provide IntelliSense for and type-safe calls into the jQuery library.
  • Line 3 declares a module, TypeScript.Revealed. Modules function much the same way as namespaces do in C# or Visual Basic. However, there’s no equivalent using or imports statement in TypeScript, so a function must be called using its fully qualified name, in this case TypeScript.Revealed.Chapter1.DisplayDate (line 12).
  • Line 4 declares a simple class, Chapter1, much as we would create a class in C#.
  • Line 6 declares a variable, currentDate, and gives it a type Date. The TLS extension now regards this as strongly typed and will flag an error if you try to use it like an Array or add it to a Number, for example.

These features are arguably the most important language features in TypeScript, but there are many more besides, as you’ll see in this chapter.

The Type System

TypeScript may bring a level of maintainable structure to JavaScript development through its class and module features, but it is the optional static typing and type inference system that TypeScript—through the TypeScript Language Service extension for Visual Studio (TLS for short)—brings to design-time development that will reduce the number of errors found at compile time as well as the tooling. Thanks to a strongly typed system, the TLS extension can also support many features—such as IntelliSense, refactorings, renamings, go-to definitions, and cross-file compiling—that .NET developers take for granted these days.  Of course, you have the option not to apply types to any of your TypeScript code and have it behave dynamically as raw JavaScript does, but if you do start to define types, the TLS can infer further types and provide refactoring information. In fact, even if you don’t explicitly define a type for a variable, the TLS will attempt to infer one from the values and literals you give it according to the (many) rules laid out in the TypeScript specification. For example:

var a = 1584;
// type inferred as Number
 
var b = { height : 180, name : "Gandalf" };
// type inferred as { height : Number; name : String; }

Let’s not get ahead of ourselves, however. TypeScript inherits its five primitive types—Number, String, Boolean, undefined, and null—from JavaScript. As you saw in Chapter 1, because TypeScript is strongly typed, some of the classic JavaScript gotchas caused by JavaScript converting values on the fly are easily caught by the TLS extension and by the compiler. For instance:

var oneNumber : number = 1;       // creates a Number
var oneString : string = "1";     // creates a String
 
console.log(oneNumber == oneString);
// JavaScript would output 'true'. TypeScript won't compile this

As you can see from the example, a variable’s type is declared by using the var keyword and a postfix syntax rather than the prefix system we know from C# or Visual Basic. The type is separated from the name of the variable by a colon.  When you declare a variable, you have four options:

  • Declare its type and value (as a literal) in one statement.
  • Declare its type but no value.  The value will be set to undefined.
  • Declare its value but no type. The variable will be of type Any (that is, an old-school dynamic JavaScript variable), but its type may be inferred by the TLS based on its value.
  • Declare neither value nor type. The variable will be of type Any, and its value will be undefined.

For example:

var numberOfDwarves : number = 13;// Option 1. Type and value set
var numberOfGoblins : number;// Option 2. Type set but value undefined
var numberOfHobbits = 1;// Option 3. Value set and type inferred as number
var lengthOfFilm;// Option 4. Neither value nor type set yet.

Variables can also be declared with an interface type or a class type. You’ll look at both interfaces and classes later in this chapter. For now, you’ll examine the other possibilities—that a variable is of a primitive, function, or array type.

image Note  Variables have varying levels of scope in TypeScript, depending on where they are declared. Those declared outside functions, classes, and modules have global scope and can be accessed by all code in the application or in the web page on which it is declared.

Primitive Types

From a .NET perspective, JavaScript primitive types are slightly limited. The String and Boolean types are as you would expect, and the Number type is the equivalent of System.Double; there is no integer type in TypeScript or JavaScript.

var heroName : string = "Bilbo Baggins";
var orcsNeedAShower : bool = true;

The null and undefined types, however, are often a source of confusion. In a nutshell, undefined is equivalent to a variable having no value or object assigned to it, while null means an object has been assigned to the variable but that object is null (a la C#). So undefined is a value, while null is an object. To add a little more confusion, variables cannot be typed as Undefined or Null, but they can be given the values undefined and null explicitly (or by not setting a variable’s value when declaring it, in undefined’s case).

var numberOfGoblins : number;
// Same as var numberOfGoblins : number = undefined;

var ageOfGandalf = undefined;
// Same as var ageOfGandalf : any = undefined;
 
var ageOfSaruman : number = null;
// Primitives can be given the null value (confusingly)
 
var ageOfRadagast = null;
// Same as var ageOfRadagast : any = null;
 
var error1 : Null; // Throws an error
var error2 : Undefined; // Throws an error

image Note  There’s a collection of JavaScript gotchas around null and undefined at www.codeproject.com/articles/182416/A-Collection-of-JavaScript-Gotchas#nullundefined if you aren’t already confused enough.

The Any Type

I’ve mentioned it in passing so far, but there’s one more variable type to cover in detail. If you do not assign a variable a type, the TLS assigns it the Any type. If you do this, or set it explicitly, it will be allowed to act as any standard, dynamically typed JavaScript variable that can be set any value, from complex objects to a simple number.

var changeling;      // assigned type Any implicitly
var morph : any;     // assigned type Any explicitly

When a variable is given the Any type, the TLS will try to infer the type of the variable you are using in order to make sure you’re not doing anything potentially incorrect with it. However, if you do want to use it as a dumping ground for random values, that’s entirely up to you. JavaScript allows this, and TypeScript does too.

Of course, as TypeScript actually gives you a strongly typed diving board over the shark-infested waters of dynamic JavaScript variables, you should always prefer to assign a specific type to a variable rather than the Any type. You’ll get no IntelliSense help or red squigglies to help you deal with Any type variables correctly. Future iterations of the TypeScript compiler will also verify that there are no spurious variables of type Any in your program.

Arrays

If you’re a C# developer, arrays use the same syntax and literals as you’re already used to. An array type is defined by using the name of the type in the array followed by a pair of square brackets. For example:

var emptyArray: any[] = new Array();

Array literals are written as comma-separated lists surrounded by a pair of square brackets:

var actors: string[] =  ["Martin Freeman", "Ian Holm", "Elijah Wood"];

image Tip  Always prefer to declare an untyped array as any[] rather than just assign it to new Array().

You can assign only one type to all the elements in an array. You can’t state a set of allowed types. You can, however, declare the array to be of type any[], in which case the array can hold any value of any type—numbers, objects, functions, other arrays, and so forth.

Arrays are of variable length, and not fixed length as in C#. You can retrieve the number of items in the array by using its length property and add or remove items with its push or  pop functions as you would in .NET.

You can iterate through the items in an array by using either for or for..in loops as demonstrated here:

// standard for loop
for (var i = 0; i < actors.length; i++)
{
    console.log(actors[i]);
}

// for..in loop
for (var actor in actors)
{
    console.log(actor);
}

Note that you can reference an item in an array by using both a number and a string. However, using a string indexer returns a value of type any rather than one of type string. For example:

var asExpected = actors[0];        // returns string "Martin Freeman"
var gotcha = actors["Ian Holm"];   //  returns value of type any

The same is true of all arrays of type T[]. Using a numerical index returns a value of type T. Using a string index returns a value of type Any.

Arrays of Arrays

Multidimensional arrays, or arrays of arrays, are also allowed in TypeScript, and declared to be of type T[][]. For example:

var trilogies: string[][] = [
    ["An Unexpected Journey", "The Desolation of Smaug", "There and Back Again"],
    ["The Fellowship Of the Ring", "The Two Towers", "The Return Of The King"]
];

As you can see, multidimensional array literals can quickly appear a bit confusing (especially when dealing with arrays of functions, or arrays of arrays of functions), but they remain simply a comma-separated list of comma-separated lists. If you prefer, you can build them up programmatically:

var trilogies: string[][] = new Array();
trilogies.push(["An Unexpected Journey", "The Desolation of Smaug", "There and Back Again"]);
trilogies.push(["The Fellowship Of the Ring", "The Two Towers", "The Return Of The King"]);

image Note  The TypeScript spec says, “Arrays are obviously a prime candidate for generic treatment. We expect array type literals to eventually become syntactic sugar for generic instantiation of a global interface type Array<T>.”

Casts

One of the more common issues of working with strongly typed variables is the need to cast between types from time to time. Naturally, it’s preferable to use a method such as ToString or ParseInt to do the conversion for you, but there are occasions in both .NET and TypeScript programming when an explicit cast (boxing) is required. In the TypeScript specification (section 4.13), it’s referred to as type assertion, but it’s still casting. The syntax is to put the target type between < and > symbols and then place it in front of the variable or expression that returns a value. For example:

var a : int = <int>SomeNumberAsAString;

Those familiar with client-side web programming using JavaScript and the Document Object Model (DOM) may occasionally come across issues as a result of the strongly typed nature of TypeScript that would not arise in plain JavaScript. For example, in the Stack Overflow post- http://stackoverflow.com/questions/12686927/typescript-casting-htmlelement, a user copied some working DOM-manipulation code straight from JavaScript into TypeScript and was told a method he was calling on an HTML element did not exist. It transpired that JavaScript was transparently converting the element his code selected (an HTMLElement) into a different type that did define the method (an HTMLScriptElement), but as TypeScript would not do that without an explicit cast, an error occurred. However, we can cast the type of the returned object to an HTMLScriptElement.

var script = <HTMLScriptElement>document.....

Don’t forget that type assertions are only a design-time feature of TypeScript designed to make sure that you can indeed cast from one type to another and that what you call on the newly cast object is possible. TypeScript uses structural (duck) typing, so if a Duck object has the same methods and properties as a Pig object—Oink(), Snort(), and so forth—apart from it being a very strange duck, it can be cast into a Pig object.

Type assertions check for assignment compatibility in both directions. Thus, type assertions allow type conversions that might be correct, but aren’t known to be correct.

Section 4.13 of the TypeScript specification

In TypeScript, then, you won’t be able to cast an integer to an HTMLScriptElement, for instance, but you would in JavaScript, and you would find out something was wrong only at runtime.

Ambient Declarations

As you saw in Chapter 1, you can use ambient declarations to create a placeholder variable to represent an object or other variable not declared in full in your current TypeScript file. For example, you might want to use a third-party JavaScript library that does not have its own declaration file. You can use an ambient declaration to introduce an object created in that library into your script without having the compiler throw an error because it does not know what the variable is referring to. For example, if you wanted to add a reference to the global jQuery object $ without adding a reference to the jQuery declaration file, you would write this:

declare var $;

The key is to prefix the variable declaration with the declare keyword, as you saw in the example at the end of Chapter 1.

Functions

Perhaps the biggest difference between JavaScript and today’s .NET languages is the way in which functions are used. Both use them to group code statements together for reuse, but in JavaScript (currently), functions are also the only way of structuring content. Classes and modules in TypeScript are compiled down to constructor functions that put properties on an object instance. Closures (access to local function variables from outside the function) are used to encapsulate data. Functions are also first-class objects in JavaScript/TypeScript, so they can be assigned to a variable, passed as a parameter (for example, as a callback), or returned from a (factory) function.

TypeScript distinguishes between three types of functions:

  • Top-level (global) functions are defined outside a class or module declaration. They have global scope and can be called at any point from code. They have a name.
  • Class methods are named functions declared as part of an object class. They can be called only after the class has been instantiated unless they are marked as static.
  • Anonymous functions do not have a name. They are typically used when declaring a function that will be used in only one place.

As it does with variable declarations, TypeScript allows you to strongly type a function’s signature with the net result that the TLS alerts you to any invalid passed parameter values. It also provides you with IntelliSense for those parameters when you are calling the function (which turns out to be invaluable when trying to pass callback functions). For example:

function CelsiusToFahrenheit(celsius: number): number {
    return (celsius * (9 / 5) + 32);
}
 
function EncodeAndLog(
    unencodedString: string,
    encodefn: (rawString: string) => string): void {
      console.log(encodefn(unencodedString));
}
 
function CalculateArea(rect: {width: number; height: number;}): number {
    return rect.width * rect.height;
}

We can provide a type for each argument in a function’s signature and its return type by using the same postfix syntax for typing variables. The parameters for the function are declared as a comma-separated list within a pair of parentheses, followed by a colon and the return type. If the function does not return a type, you should indicate this by using the Void type. As with variables, the TLS will do its best to infer the correct signature for those functions not given types explicitly. Just hover the cursor over a parameter or the return value, and a tooltip will appear, displaying what the TLS believes is the correct type, although it will default to the Any type if its use is ambiguous in some way.

function fnName(
    param1name : param1type,
    param2name : param2type,
    ...,
    paramNname : paramNtype) : returnType { ... }

Indeed, the net result is exactly the same as you would see declaring a type for a function expression (a variable holding a function) with one optional change. The return type of the function can be prefixed by either a colon or an arrow (=>). For example:

// Set to undefined
var logFn : (rawString: string) => void;
 
// Set to named function
var converterFn : (celsius: number) => number = CelsiusToFahrenheit;
 
// Set to anonymous function
var areaFn : (ellipse: {r1: number; r2: number;}) => number =
    function(ellipse) {
        return Math.PI * ellipse.r1 * ellipse.r2;
    };
 
// An array of typed functions - both syntaxes demonstrated
var areaCalculators : { (s: Shape) => number; }[];
var areaCalculators : { (s: Shape) : number; }[];

Functions can be assigned to variables only if their signature completely matches the “brand” of the variable. In other words, all parameter names and types must match—not just types. The comparison is not based solely on the position of the parameters and their types.

image Note  TypeScript does not currently support a documentation system such as ­XMLDoc for .NET. Discussion is continuing at http://typescript.codeplex.com/­discussions/397660 as to which documentation style should be used.

Variations on a Signature

TypeScript functions may take three types of parameters when defined:

  • Positional parameters
  • Optional parameters
  • Rest parameter

Positional Parameters

Positional parameters, declared as paramName[:paramType], which you’ve used exclusively up to now, require you to specify a value for them when you call the procedure:

function CelsiusToFahrenheit(celsius: number): number {
    return (celsius * (9 / 5) + 32);
}

Optional Parameters

Optional parameters are declared with a postfix question mark: paramName? [: paramType]. These should be set as the last arguments in a call to a function:

var kelvin: number;
function CelsiusConverter (celsius: number,
    calculateKelvinToo?: bool = false): number {
    if ( calculateKelvinToo) { kelvin = celsius273.1;  }
    return (celsius * (9 / 5) +32);
}

JavaScript doesn’t have an explicit hasValue method as C# does to check whether an optional parameter has been given a value. The best way to overcome this is to specify a default value for the parameter. If the function is called without a value assigned to the optional parameter, it is given the default value.

function CelsiusConverter (celsius: number,
    calculateKelvinToo?: bool = false ): number {
   ...
}

If you’d prefer not to assign a default value, you can check whether a parameter has been given a value by comparing it to null and undefined:

var kelvin: number;
function CelsiusConverter (celsius: number,
    calculateKelvinToo?: bool): number {
    if ( calculateKelvinToo !== null && calculateKelvinToo !== undefined ) {
       if ( calculateKelvinToo) { kelvin = celsius273.1;  }
    }
    return (celsius * (9 / 5) +32);
}

Note that in this example, we take advantage of the fact that functions can access variables outside their scope, to make up for the fact that TypeScript/JavaScript has no equivalent to an out parameter in .NET.

Rest Parameter

The rest parameter, declared as ...paramName[:paramType] (three periods), represents the last argument in a call to a procedure and can hold an arbitrary number of arguments in addition to those specified before it. These arguments are held in an array of type T[]  that can be iterated over by using a for loop:

function CountDwarvesTallerThan(minHeight: number, ...dwarves: Dwarf[]) : number {
    var count: number = 0;
    for (var i = 0; i < dwarves.length; i++) {
        if (dwarves[i].height > minHeight) {
            count++;
        }
    }
    return count;
}

Remember not to use a for..in loop over your rest parameter, as this will turn your arguments into strings rather than the desired type (unless, of course, they are strings anyway, in which case, job’s a good ’un).

Function Overloads

TypeScript also supports the overloading of functions, albeit without quite the same efficacy as .NET. JavaScript has no concept of method overloading itself, so while the TLS enforces strong typing against the various overloads at design time, you still have to write a generalized form of the function that will work in “raw” JavaScript that discerns which one of the overloads you’re using and operates accordingly.

All of this explanation really requires an example to clarify the picture.

In .NET, a method can be overloaded by creating multiple functions with the same name, but each working with different parameter types. For example, in C# I might overload a function called CalculateArea like so:

public double CalculateArea(Square shape)
{
   return shape.x * shape.y;
}
 
public double CalculateArea(Ellipse shape)
{
   return shape.r1 * shape.r2 * Math.PI;
}
 
public double CalculateArea(Triangle shape)
{
   return 0.5 * shape.x * shape.y;
}

The function name is the same, but the signature of each overload (that is, their name and parameters but, crucially, not the return type) is different.

In TypeScript, a method is overloaded by stacking each signature variant of the function on top of a single implementation of the function. For example:

function CalculateArea(shape : Square) : number;
function CalculateArea(shape : Ellipse) : number;
function CalculateArea(shape : Triangle): number;
function CalculateArea(shape : Shape) : number {     //  <−− The crucial line!!
   if (shape instanceof Square) {
     return (<Square>shape).x * (<Square>shape).y;
   }
   if (shape instanceof Ellipse) {
     return (<Ellipse>shape).r1 * (<Ellipse>shape).r2 * Math.PI;
   }
   if (shape instanceof Triangle) {
     return 0.5 * (<Triangle>shape).x * (<Triangle>shape).y;
   }
   throw new TypeError("Unsupported type!");
}

The key differences here are as follows:

  • You must explicitly type your functions—parameters and return types—if you want to overload them.
  • There is an additional generalized function signature at the bottom of the stack.
  • You need to generalize all parameters and the return type. Use optional parameters if the number of parameters varies plus default parameter values as appropriate.
  • You need to generalize the return type as well: it is key.
  • The signature of the implementation has to be compatible with all of the overloads.
  • You must type-check the parameters within the function body. Again, the final JavaScript method is not type safe, so TypeScript forces you to write overloaded functions in this way. Use typeof to test for primitive types and instanceof for everything else.

Basically, you are creating just one function and giving it a number of signatures so that TypeScript doesn’t give compile errors when you pass it differently typed parameters. This means that while you’re still writing the code, the TLS will strongly type the function calls correctly, but when compiled to JavaScript, the concrete function alone will be visible. For example, the overloaded CalculateArea function in the preceding code compiles down into the following JavaScript:

function CalculateArea(shape) {
    if(shape instanceof Square) {
        return (shape).x * (shape).y;
    }
    if(shape instanceof Ellipse) {
        return (shape).r1 * (shape).r2 * Math.PI;
    }
    if(shape instanceof Triangle) {
        return 0.5 * (shape).x * (shape).y;
    }
    throw new TypeError("Unsupported type!");
}

And, unless you call CalculateArea from outside your TypeScript, you know that all the calls to this concrete function will work with the objects sent to them because all calls have been vetted by the TLS. (They may throw a TypeError, but that still counts as working in this case.)

Classes

As .NET developers, we take for granted that we can group properties, methods, and events into classes representing objects that interact within a system. It is fundamental to the concept of object-oriented programming that we can create a class, instantiate it, call methods, and handle events on it. Unless we’re using unsafe code, we even take for granted that the garbage collector will free the memory the object used after it is no longer in use. JavaScript has been a capable, object-oriented language for some time now (as Douglas Crockford has taken pains to point out at http://javascript.crockford.com/inheritance.html and http://javascript.crockford.com/prototypal.html). However, for Java, C#, and Visual Basic developers, the way in which JavaScript implements said object-oriented capabilities is awkward. Fortunately, TypeScript implements a class syntax system that is very similar to C# and also very close to the class proposal in ECMAScript 6.

To define an empty class, the syntax is simply as follows:

class SimpleWebSocket {
}

When compiled into JavaScript, it looks like the following code. The TypeScript version is clearer and carries the bonus that you can cross-compile the code into JavaScript compliant with both ECMAScript 3 and ECMAScript 5 (and ECMAScript 6 too, in time, you would imagine) for free.

var SimpleWebSocket = (function () {
    function SimpleWebSocket() { }
    return SimpleWebSocket;
})();

Enough looking at what JavaScript is generated. The point is to get away from that. Have a look at JavaScript: The Definitive Guide by David Flanagan (O’Reilly Media, 2011) if you want to understand it.

image Note  TypeScript has no equivalent to structs in .NET.

Class Members

TypeScript classes provide for the same three types of class members you would expect to find in .NET:

  • Properties and fields to store data
  • Methods to define behavior
  • Events to provide interactions between different objects and classes

Let’s look at each of these in turn.

Properties and Fields

A common practice in .NET is to create a private field to store data and then a public property with get and set functions that allow you to implement any rules or constraints for retrieving or writing to the data in the field. In JavaScript, the option to create such a public property has only existed since ECMAScript 5 was released. As the majority of JavaScript VMs out there are still only ECMAScript 3–compliant, creating properties with getters and setters is not often done. In TypeScript, however, implementing public properties over private fields is allowed and enforced through the TLS extension in Visual Studio. The compiler just leaves them in when compiling down to ECMAScript 5 code and refactors them into methods when compiling into ECMAScript 3 code.

For example, to define a simple read-write field, we have this:

class SimpleWebSocket {
    serviceUrl: string;
}

If you wish to create a public property over a private field, you can include additional logic for reading and writing it in the getter and setter methods:

class SimpleWebSocket {
    private serviceUrl: string;
    get ServiceUrl(): string {
        // Fields and Properties are always referred to in class prefixed with 'this.'
        return this.serviceUrl;
    }

    set ServiceUrl(value: string) {
        this.serviceUrl = value;
    }
}

Points to note here:

  • The serviceUrl private field is private only at design time. The TLS can enforce the intention of a private field only at design time, as JavaScript itself has no concept of a private member field. However, if the variable acts as a private one in TypeScript, it will do similarly in the compiled JavaScript.
  • You cannot add a return type to a setter method. (It is assumed to be null.)
  • Omitting the get or set property method does not mean that property is read-only or write-only in JavaScript as it would be in C#. As noted in the first point, you can still access the read-write private variable directly.

Whether you use fields or properties, you will always need to refer to them inside the class by using the this.name syntax, as demonstrated in the preceding code.

Methods

To define a method of a class, simply add a function in the class without the function keyword you would use if declaring a global function as demonstrated earlier in this chapter:

class SimpleWebSocket {
    Close(code: number, reason: string): void {
        console.log(code + " : " + reason);
        this.state = this.SocketState.CLOSED;
    }
}

As noted earlier, if you wish to overload a class method, you do so by stacking signatures for that method on top of each other and implementing a generalized form of the many functions:

class SimpleWebSocket {
    Close(code: string): void;
    Close(code: string, reason: string): void;
    Close(code: number): void;
    Close(code: number, reason: string): void;
    Close(code: any, reason?: string): void {
        var logEntry: string = code.toString();
        if (reason) { logEntry += (" : " + reason); }
        console.log(logEntry);
        this.state = this.SocketState.CLOSED;
    }
}

image Note  If you want to add a custom method to a built-in JavaScript object, you should do so on the object’s prototype property. However, this breaks the TLS type analysis. See http://typescript.codeplex.com/workitem/4 for the current workaround and status for this bug.

Arrow Functions

Another JavaScript gotcha that TypeScript helps us fix at design time concerns the this variable. You’ve seen previously that to reference class members from within other class members, you prefix them with this:

class SimpleWebSocket {
    state : number;
 
    Close(code: number, reason: string): void {
        console.log(code + " : " + reason);
        this.state = this.SocketState.CLOSED;
    }
}

However, when dealing with the DOM, it is possible to lose sight of what your this variable is referring to because its lexical scope has changed. Take, for example, any of the window.onmouse* events—onmouseover, onmousemove, and so forth.

When assigning any of these events a handler function, the function  overrides this to refer not to your class, but to the element your mouse has just interacted with. In the following example, then, the function beginMenuTest() tries to alter the value of the UITester class property menuTouches twice. First it correctly resets it to zero, but the second reference is within an anonymous function handling an onmouseenter event. Within the scope of the anonymous function, this.menuTouches refers to the menuTouches property of the menu object the mouse has just moved over—which probably doesn’t exist.

class UITester {
    menuTouches : number;
    sidebarTouches : number;
 
    beginMenuTest(): void {
        this.menuTouches = 0;   // Right!!
        menu.onmouseenter = function (e) {
            this.menuTouches++;  // Wrong!!
        }
}

TypeScript provides the solution by allowing us to use arrow functions (which have a very similar syntax to lambda functions in C#) that retain a lexically scoped this variable.

In the beginSidebarTest() method, both references to this.sidebarTouches refer to the property of the UITester class:

    beginSidebarTest() : void {
        this.sidebarTouches = 0;  // Right!!
        sidebar.onmousemove = e => {
            this.sidebarTouches++;  // Still right!!
        }
    }
}

Note that the TLS warns you to use arrow functions only indirectly. You won’t see any red squigglies highlighting something wrong with beginMenuTest, because there isn’t. However, the TLS will infer this to be of type UITester in the first instance and of type any in the second instance (within the function handler), so IntelliSense will appear to stop working.

Constructors

For every class implementation, there is at least one constructor function that is executed automatically when an object of that class is created. Taking its cue from ECMAScript 6, class constructor functions are identified by using the constructor keyword:

class SimpleWebSocket {
    constructor (url: string) {
        this.serviceUrl = url;
        this.state = this.SocketState.OPEN;
        this.messagesSent = 0;
    }
}

You can also overload your constructor, but don’t forget to treat it as you would any other overloaded method. At the bottom of your stack of signatures must be a generalized function providing the concrete implementation of the constructor used when compiled into JavaScript:

class SimpleWebSocket {
    constructor ();
    constructor (url: string);
    constructor (url?: string) {
        if (url !== null && url !== undefined) {
            this.ServiceUrl = url;
        }
        else {
            this.ServiceUrl = " http://localhost:80 ";
        }
        this.state = this.SocketState.OPEN;
        this.messagesSent = 0;
    }
}

Don’t forget to reference your class properties and fields by using the this prefix.

Events

You saw in Chapter 1 how handler methods can be attached to events by using the DOM or jQuery:

// Using the DOM
document.getElementById("btnGo").addEventListener("click", displayDate);
 
// Using jQuery
$("#btnGo").click(displayDateJQ);

In C#, a class event is declared by using the event keyword and by then also declaring a delegate function to be called when the event has occurred:

// Declare the delegate (if using non-generic pattern).
public delegate void SampleEventHandler(object sender, SampleEventArgs e);
 
// Declare the event.
public event SampleEventHandler SampleEvent;

In TypeScript, these two declarations are combined into an optional class property defined to be of a certain function type but currently undefined:

class SimpleWebSocket {
    onOpen?: (ev: Event) => any;
}

Access Modifiers

You can add public and private accessors to the fields, properties, and methods inside a class:

  • public (the default) indicates that the class member is available to all code in another module after it has been exported.
  • private indicates that the class member is available only to other code in the same assembly.

For instance:

class SimpleWebSocket {
    private serviceUrl : string;
}

Note, however, that, as discussed earlier, the class member will not actually be accessible only to the class when compiled as JavaScript. It is only at design time that the TLS enforces the concept of private members.

Instantiating Classes

To create an object, you can instantiate a class by using the new keyword, exactly as you would in C#:

var TestSocket : SimpleWebSocket = new ←    SimpleWebSocket(" http://testthis.com");

After you’ve created the object, you can assign and retrieve that instance’s properties and invoke its methods:

console.log("Opened a connection to " + TestSocket.ServiceUrl);
TestSocket.Send("Test the socket");
TestSocket.Close(200, "OK");
console.log(TestSocket.state.toString());

Check whether you can create an instance of a class by using an object literal.

Static Classes and Members

You can use the static keyword to indicate that a class property, field, or function is shared by all instances of a class, as you would in C# or by using the shared keyword in Visual Basic. To access the static member, use the name of the class without instantiating it:

class SimpleWebSocket {
    static origin: string;
 
    static ThrowError(code: number) {
        this.Close(code);   // NB. You can call non-static methods from a static method.
    }
}
 
SimpleWebSocket.origin = "Program Name";
SimpleWebSocket.ThrowError(404);

Note that if you are referencing the static member from within a class function, you do not need to prefix it with this:

class SimpleWebSocket {
    constructor (url: string) {
        this.ServiceUrl = url;                      // non-static members. 'this' required
        this.state = this.SocketState.OPEN;
        this.messagesSent = 0;
        SimpleWebSocket.origin = "This Program";    // static member. No 'this' required.
    }
 
    static origin: string;
}

Unlike .NET, static methods do not have to call only static methods or reference only static fields and properties. Also, while there is no explicit way to label a class as static, it is perfectly correct to label every member of a class as static, which amounts to the same thing. There is just no way to have the TLS check that all members of a class are static. You have to do it yourself.

Object Literals vs. Anonymous Types

In .NET, anonymous types allow you to create objects without a class definition. The compiler then generates a class for you that has no name and only the properties you specify when creating the object. For instance:

var simpleAnonymousObject = new { This = 123, That = "A string" };

In TypeScript, a similar system occurs through the use of JSON-formatted object literals. A variable can be assigned an object literal—the new keyword is not required in TypeScript—and the TLS then infers the type of that variable.

var anon = { This : 123, That : "A String" };

Hover your cursor over the anon variable name and you’ll see the inferred type, which in this case is

{
   This: number;
   That: string;
}

Inheritance

JavaScript mirrors the concept of class inheritance through the creation of a prototype chain on your objects. While TypeScript compiles the relationship between classes and base classes into similar code, the actual syntax to indicate that relationship in TypeScript is more akin to that of .NET, using the extends keyword to indicate an inheritance relationship.

class ComplexWebSocket extends SimpleWebSocket {
}

If you need to reference the base class from within the derived one—for example, to access the base class’s (constructor) function, use the super keyword:

class ComplexWebSocket extends SimpleWebSocket {
    constructor (url: string, protocol: string) {
        super(url);            // calls the base class constructor function
        this.protocol = protocol;
    }
 
    protocol: string;
 
    ThrowError(code: number) {
        // some code
        super.Close(code);     // calls Close on the base class.
    }
}

In the case of instantiating an object of the derived type, the base class’s constructor function will be called automatically if it isn’t done so explicitly (as shown in the preceding code) within its own constructor.

image Note  If you follow the same practice as in .NET of writing one class per file, you’ll need to make sure to add a reference to the file containing the parent class. TypeScript won’t look around the project for the class you’re referencing automatically. It must be told where to look.

/// <reference path="SimpleWebSocket.ts"/>
class ComplexWebSocket extends SimpleWebSocket {
   ...
}

Overriding Methods from the Base Class

TypeScript allows you to override the method of a base class simply by writing another method in the derived class with the same method signature or of a valid subtype of the original signature. For instance, let’s say the base class defines the method Send as follows:

Send(data: string): void {
    console.log(data);
    this.messagesSent++;
}

If you needed to override this method, you could add either of the following to the derived class, and all would be well. There is no need to mark it specifically as an overridden method as you would in C# or Visual Basic with an override or Overrides keyword, respectively:

Send(data: string): void {     //  <−− matches signature in base class
    // new code
}
 
Send(data: any): void {        //  <−− is a generalized form of the function
    // new code
}

At this point, within the derived class definition, you can call the new version of Send by calling this.Send() and the base class version of it by using super.Send(). However, you do not have access to super.Send() from an instantiation of the derived class.

var cws = new ComplexWebSocket("someUrl", "http");
 
//  Calls Send defined in ComplexWebSocket or base class version
//  if not defined in ComplexWebSocket
cws.Send("a message");
 
// Throws an error.
cws.super.Send("another msg");

If you want to override a function that is overloaded in the base class, note that you must list out all the signatures for that overloaded method in the derived class as well. You can’t just override one form of the function; you must override the generalized form instead. For example, the SimpleWebSocket class defines a Close method with four overloads:

Close(code: string): void;
Close(code: string, reason: string): void;
Close(code: number): void;
Close(code: number, reason: string): void;
Close(code: any, reason?: string): void {
    var logEntry: string = code.toString();
    if (reason) { logEntry += (" : " + reason); }
    console.log(logEntry);
    this.state = this.SocketState.CLOSED;
}

To override it in the ComplexWebSocket class that derives from SimpleWebSocket, you’ll need to include this:

Close(code: string): void;
Close(code: string, reason: string): void;
Close(code: number): void;
Close(code: number, reason: string): void;
Close(code: any, reason?: string): void {
   // ^^ or a further generalized form ^^ of the function signature
   // new code
}  

Interfaces

In .NET, interfaces define a set of properties, methods, and events but do not provide their implementation. You cannot instantiate an interface as you can a class. Classes inherit from interfaces and must implement each item in the interface exactly as defined. In this way, interfaces perform a dual role in .NET:

  • As a marker that a class has a certain kind of behavior—for instance, IContainer, IEnumerable, IDisposable.
  • As a generalization of a group of classes without being a base type. For instance, you might give an interface as a function’s return type indicating that any number of object types might be returned by the function but that they will all have a minimum level of common functionality as given in the interface. This idea is a cornerstone of dependency injection.

These same purposes are true of interfaces in TypeScript, but there are significant differences in how they are used.

  • They are a design-time-only construct for the TLS to consume and provide useful type checking, IntelliSense, and refactoring services, and for us to use as a shortcut. They vanish after compilation, replaced with the object types they represent.
  • They are not inherited by classes. They are object types for application to variables, parameters, and return types.
  • TypeScript interfaces are open-ended. That is, rather like partial classes in .NET, they can be defined across several files and the combination of the parts is the final object type actually applied to variables assigned that interface.

Common-sense dictates that we’ll use interfaces as a refactoring tool for function types, behavioral sets of functions a la IComparable, and full class definitions.

image Note  There’s no convention set for naming interfaces in TypeScript, but we’ll prefix them with IFn for function types and I otherwise, as is done in .NET.

Using Interfaces as Function Types

If one of the goals of TypeScript is to make JavaScript more maintainable, then using interfaces to represent function types will definitely help. It can certainly make things more legible, and the clearer code is, the easier it is to maintain. Consider the following code:

var sayHello: (input: string) : string = function (s: string) {
    return "Hello " + s;
}
var stringUtils: { (input: string): string; }[];
stringUtils.push(sayHello);

Even though sayHello is just a function that takes a string and returns one, and stringUtils is an array of such functions, it’s not difficult to see how such types can quickly become difficult to write and interpret with more parameters, perhaps some of those being functions and arrays of functions everywhere. A parenthetical crisis!

Fortunately, we can refactor the function signature into an interface, and things become instantly more legible. And bracket free.

interface IFnStringManipulator {
    (input: string) : string;
}
 
var sayHello: IFnStringManipulator = function (s: string) {
    return "Hello " + s;
}
var stringUtils: IFnStringMaipulator[];
stringUtils.push(sayHello);

At this point, we should note that the tooling to go with this useful refactoring is a bit lacking at the moment. There’s currently no support in Visual Studio for the Extract Interface refactoring available with .NET languages. (See the discussion at http://typescript.codeplex.com/discussions/400724.) Also, IntelliSense tells you that sayHello is of type IFnStringManipulator rather than presenting the actual function signature. Of course, you can right-click IFnStringManipulator and choose Go To Definition (F12) to see what it is, but the to-and-fro between files is a pain.

Using Interfaces as Object Types

As .NET developers, it’s easy to understand why defining interfaces as behavioral sets of functions is a good idea. It’s quite easy to translate those concepts into TypeScript. For instance:

interface IComparable {
    CompareTo(obj: any): number;
}
 
interface IEnumerable {
    [index: number]: any;
}
 
interface ICloneable {
    Clone(): any;
}

And it’s not too difficult to check whether an object implements an interface either. You need to check that all the elements in the interface exist on the object.

function ImplementsICloneable (obj : any) :bool {
    return (obj && obj.Clone);
}

However, why define interfaces that represent whole classes? Why not just write classes? Well, the TLS understands and uses both classes and interfaces to provide IntelliSense and other type-related services, but there are (currently) very few third-party JavaScript libraries out there that also come in TypeScript, let alone in classes, for the TLS to consume and help us code more accurately. Hence declaration files exist, making full use of interfaces and ambient variable declarations to represent complete classes in these libraries where TypeScript classes are not available.

Rambling aside, interfaces representing object types can contain placeholders for the following:

  • Constructors
  • Fields
  • Optional fields
  • Methods
  • Overloaded methods
  • Indexers

As in .NET, you cannot add static members to interfaces, and currently you cannot specify that a field should be private and accessed through getter and setter methods. To demonstrate, here’s the nearest interface equivalent for the SimpleWebSocket class we’ve used in this chapter.

// Entire Class Interface
interface IWebSocket {
    // use 'new' to indicate constructor
    new (url: string) : SimpleWebSocket;
 
    // fields
    state: number;
    messagesSent: number;
 
    // optional fields
    protocols?: string;
 
    // properties can be added
    // but getters and setters cannot be enforced
    ServiceUrl: string;
 
    // methods
    Send(data: string): void;
    onError(errorCode: number, message: string): void;
 
    // 'overloaded' method
    Close(code: string): void;
    Close(code: string, reason: string): void;
    Close(code: number): void;
    Close(code: number, reason: string): void;
 
    // indexer - socket[12] returns message #12 perhaps
    [index: number] : string;
}

Note how we use the new keyword to indicate a constructor function in the interface rather than constructor. Also the entry for the overloaded Close method does not include the generalized function signature we actually need to use to implement it.

Combining Interfaces

In .NET, a class can inherit more than one interface. In TypeScript, an interface represents an object type. Variables and parameters can’t have more than one object type, so how can we create a variable based on IEnumerable and ICloneable, for instance? The answer is to create another interface that does inherit from both IEnumerable and ICloneable:

interface IEnumerableClone extends IEnumerable, ICloneable {
    // any overrides or additional items
}

Now you can set an object to be of the new derived type:

var BobaFett : IEnumerableClone;

An interface can inherit from zero or more base interfaces. Inheritance is expressed using the extends keyword followed by a comma-separated list of the base interfaces. As with classes, you can choose to use the base interface members in the new child interface or to hide them using new declarations. The TypeScript specification (section 7.1) lays out the rules for hiding base interface members:

  • A base interface property is hidden by a property declaration with the same name.
  • A base interface call signature is hidden by a call signature declaration with the same number of parameters and identical parameter types in the respective positions.
  • A base interface construct signature is hidden by a construct signature declaration with the same number of parameters and identical parameter types in the respective positions.
  • A base interface index signature is hidden by an index signature declaration with the same parameter type.

The following constraints must be satisfied by an interface declaration, or otherwise a compile-time error occurs:

  • An interface cannot, directly or indirectly, be a base interface of itself.
  • Inherited properties with the same name must have identical types.
  • The declared interface must be a subtype of each of its base interfaces.

The net result is that if you’re inheriting from two or more base interfaces that define items of the same name but different types, your child interface needs to redefine that item by using a more generalized form, much the same as you need to do when overloading a method.

Modules

Whether you’re more familiar working with System.Web or System.IO, if you develop against the .NET Framework, you’re working with namespaces all the time. TypeScript supports some of the ECMAScript 6 functionality for modules.

The basic module declaration is exactly the same as for classes or interfaces, but uses the module keyword instead of class or interface. However, modules can also be nested, unlike classes and interfaces. For instance:

module MyWebApp {
    module DataAccess {
        // code here
    }
    module BusinessLogic {
        // code here
    }
    module UI {
        // code here
    }
 
    // global code here
}

Of course, we don’t often actually nest modules in .NET, preferring to write code in files pertaining to a single class or web page in a namespace. The preceding structure can be written as follows in TypeScript, using the additive names for the modules:

module MyWebApp.DataAccess {
        // code here
}
 
module MyWebApp.BusinessLogic {
       // code here
}
 
module MyWebApp.UI {
        // code here
}
 
// global code here

However you prefer to write them, modules can contain any of the language features you’ve seen in this chapter so far and two more:

  • Classes
  • Interfaces
  • Global functions and variables
  • Ambient declarations
  • Import declarations
  • Export declarations

In terms of structuring your code, they are effectively the same as any namespace in .NET, both logically and physically. If you wish to build modules across many files as you would namespaces (for instance, to write one class per file), you can do that too. Any elements within the same module can be accessed by each other. However, if they are located in separate files, you will need to add a reference from the top of one file to the other.

For example, you saw in the section on class inheritance that by keeping to a rule of one class per file, the child class must include a reference to that of the parent class before it will compile:

/// <reference path="parentClass.ts" />

As long as the <reference>s are there, the TLS knows and the compiler will pick up the reference chain. However, we must remember that class names must be fully qualified when written inside a module. For instance:

In SimpleWebSocket.ts:
 
module MyWebApp.BusinessLogic {
    class SimpleWebSocket {
        // code
    }
}
 
In ComplexWebSocket.ts:
 
/// <reference path="SimpleWebSocket.ts" />
module MyWebApp.BusinessLogic {
    class ComplexWebSocket extends MyWebApp.BusinessLogic.SimpleWebSocket {
    }
}

Although, as it happens, referencing a class name in the same module is an exception to a golden rule.

If you want to instantiate any class, reference any interface, use any global method, or access any global variable within a module from a different file, a different module or from global code, you will need to export it.

TypeScript considers each declaration within a module as private unless you expose it to other code by exposing it using the export keyword. The exceptions to this are class members that become accessible when you mark the class declaration with export.

In SimpleWebSocket.ts:
 
module MyWebApp.BusinessLogic {
 
    export class SimpleWebSocket {
       ...
    }
 
   // functions global to module but not in a class
   // must be exported individually
   export function Initialise() {}
   export var CurrentVisitors : number;
}
 
In another file:
 
/// <reference path="SimpleWebSocket.ts" />
 
// requires class is exported
var TestSocket = new MyWebApp.BusinessLogic.SimpleWebSocket("http");
TestSocket.Send("Some info");
TestSocket.Close("123");
console.log(TestSocket.state);
 
// Static class property
MyWebApp.BusinessLogic.SimpleWebSocket.origin = "here";
 
// In MyWebApp.BusinessLogic module but not in class
MyWebApp.BusinessLogic.Initialise();
MyWebApp.BusinessLogic.CurrentVisitors++;

The TLS does a good job of letting you know you’ve forgotten to export something. Generally, you’ll get a good case of the red squigglies if you’re attempting to access code in a module that hasn’t been exported.

However, you are stuck with using the fully qualified names for module-scoped functions, interfaces, classes, and variables, as shown in the preceding code. TypeScript has no direct equivalent of the C# using or Visual Basic Imports statement. It is possible, though, to use the TypeScript import statement to create an alias for the module. For example:

/// <reference path="SimpleWebSocket.ts" />
 
import BL = MyWebApp.BusinessLogic;
 
// class declaration
var TestSocket = new BL.SimpleWebSocket("http");
 
// Static class property
BL.SimpleWebSocket.origin = "here";
 
// Module-scoped function and property
BL.Initialise();
BL.CurrentVisitors++;

image Note  These import declarations for your own modules are evaluated lazily. They can reference modules that haven’t been instantiated yet, but you can’t then use them to reference items in that module until the module has been instantiated.

All of this brings us to the point that so far in this section we have been dealing solely with modules we have written for the same application. TypeScript divides modules into two categories:

  • If a module does not contain export statements (but likely references those that do), it is an internal module.
  • If a module does contain export statements, it is an external module.

External modules also describe third-party JavaScript modules that we’ve not written but wish to use. For example, if we are writing a Node.js application, we will need to import the (public/exported) contents of the main node module to access its basic functionality. Alternatively, we might want to use Modernizr (modernizr.com) to check a browser’s support for new HTML5 APIs. Think of these types of external modules as you would the libraries you might add to your .NET projects using NuGet.

To use such external libraries, download the TypeScript files or declaration files (.d.ts) if they exist (or the JavaScript files if they don’t) so the TLS can carry on type checking and providing IntelliSense to your project. Then you import the library into your TypeScript file using a slightly different syntax to that used earlier:

import ModuleAlias = module('ModuleName'),

In this case, the ModuleName in question is the location (absolute or relative) of the file containing the target module to be imported minus the .js at the end:

import Modernizr = module('c:srclibmodernizr'),
import svr = module('..lib ode'),

image Tip  Section 9.4.1 in the TypeScript specification contains the full rules for module name resolution.

After the module has been imported, you reference the module’s contents in the same way as internal modules. This means that unless you’re referencing external modules written in TypeScript, you’ll want a declaration file for it as well to maintain the tooling.

image Tip  You’ll find a number of community-written declaration files at https://github.com/borisyankov/DefinitelyTyped.

One final note about modules. The TypeScript compiler will transpile your modules into one of two styles of JavaScript module:

  • CommonJS modules
  • AMD modules

If you always write TypeScript applications with HTML or use TypeScript within web applications or Windows 8 applications, you may never need to use modules for any purpose other than code structure and not need to worry about this fact. However, if you begin to write applications using Node.js, or some JavaScript dependency loaders, you’ll need to take note of which style module they use and consult Chapter 3 to make sure the compiler creates the module type you need.

Summary

In this chapter, you’ve looked at the new language features that TypeScript appends to JavaScript. You’ve seen how they mirror similar features in .NET and how they differ in certain cases as well.

In the next chapter, you’ll see how to take control of the TypeScript compiler and how to incorporate TypeScript into your existing development projects.

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

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