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:
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.
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.
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:
These features are arguably the most important language features in TypeScript, but there are many more besides, as you’ll see in this chapter.
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:
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.
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
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"];
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"]);
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:
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.
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
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 = celsius −273.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 = celsius −273.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:
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.)
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.
Note TypeScript has no equivalent to structs in .NET.
TypeScript classes provide for the same three types of class members you would expect to find in .NET:
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:
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;
}
}
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:
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.
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:
These same purposes are true of interfaces in TypeScript, but there are significant differences in how they are used.
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.
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:
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:
The following constraints must be satisfied by an interface declaration, or otherwise a compile-time error occurs:
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:
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++;
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:
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'),
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.
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:
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.
3.143.247.125