January 2015

Volume 30 Number 1


TypeScript - Understanding TypeScript

By Peter Vogel | January 2015

In many ways, it’s useful to think of TypeScript on its own merits. The TypeScript language specification refers to TypeScipt as “a syntactic sugar for JavaScript.” That’s true and probably an essential step in reaching to the language’s target audience—client-side developers currently using JavaScript.

And you do need to understand JavaScript before you can understand TypeScript. In fact, the language specification (you can read it at bit.ly/1xH1m5B) often describes TypeScript constructs in terms of the resulting JavaScript code. But it’s equally useful to think of TypeScript as a language on its own that shares features with JavaScript.

For example, like C#, TypeScript is a data-typed language, which gives you IntelliSense support and compile-time checking, among other features. Like C#, TypeScript includes generic and lambda expressions (or their equivalent).

But TypeScript, of course, is not C#. Understanding what’s unique about TypeScript is as important as understanding what TypeScript shares with the server-side language you’re currently using. The TypeScript type system is different (and simpler) than C#. TypeScript leverages its understanding of other object models in a unique way and executes inheritance differently than C#. And because TypeScript compiles to JavaScript, TypeScript shares many of its fundamentals with JavaScript, unlike C#.

The question then remains, “Would you rather write your client-­side code in this language or in JavaScript?”

TypeScript Is Data-Typed

TypeScript doesn’t have many built-in data types you can use to declare variables—just string, number and Boolean. Those three types are a subtype of the any type (which you can also use when declaring variables). You can set or test variables declared with those four types against the types null or undefined. You can also declare methods as void, indicating they don’t return a value.

This example declares a variable as string:

var name: string;

You can extend this simple type system with enumerated values and four kinds of object types: interfaces, classes, arrays and functions. For example, the following code defines an interface (one kind of object type) with the name ICustomerShort. The interface includes two members: a property called Id and a method called CalculateDiscount:

interface ICustomerShort
{
  Id: number;
  CalculateDiscount(): number;
}

As in C#, you can use interfaces when declaring variables and return types. This example declares the variable cs as type ICustomerShort:

var cs: ICustomerShort;

You can also define object types as classes, which, unlike interfaces, can contain executable code. This example defines a class called CustomerShort with one property and one method:

class CustomerShort
{
  FullName: string;
  UpdateStatus( status: string ): string
  {
    ...manipulate status... 
    return status;
  }
}

Like more recent versions of C#, it’s not necessary to provide implementation code when defining a property. The simple declaration of the name and type is sufficient. Classes can implement one or more interfaces, as shown in Figure 1, which adds my ICustomerShort interface, with its property, to my CustomerShort class.

Figure 1 Add an Interface to a Class

class CustomerShort implements ICustomerShort
{
  Id: number;
  FullName: string;
  UpdateStatus(status: string): string
  {
    ...manipulate status...
    return status;
  }
  CalculateDiscount(): number
  {
    var discAmount: number;
    ...calculate discAmount...
    return discAmount;
  }
}

As Figure 1 shows, the syntax for implementing an interface is as simple in TypeScript as in C#. To implement the interface’s members you simply add members with the same name instead of tying the interface name to the relevant class’ members. In this example, I simply added Id and CalculateDiscount to the class to implement ICustomerShort. TypeScript also lets you use object type literals. This code sets the variable cst to an object literal containing one property and one method:

var csl = {
            Age: 61,
            HaveBirthday(): number
          {
            return this.Age++;
          }
        };

This example uses an object type to specify the return value of the UpdateStatus method:

UpdateStatus( status: string ): { 
  status: string; valid: boolean }
{
  return {status: "New",
          valid: true
         };
}

Besides object types (class, interface, literal and array), you can also define function types that describe a function’s signature. The following code rewrites CalculateDiscount from my CustomerShort class to accept a single parameter called discountAmount:

interface ICustomerShort
{
  Id: number;
  CalculateDiscount( discountAmount:
    ( discountClass: string, 
      multipleDiscount: boolean ) => number): number
}

That parameter is defined using a function type that accepts two parameters (one of string, one of boolean) and returns a number. If you’re a C# developer, you might find that the syntax looks much like a lambda expression.

A class that implements this interface would look something like Figure 2.

Figure 2 This Class Implements the Proper Interface

class CustomerShort implements ICustomerShort
{
  Id: number;
  FullName: string;
  CalculateDiscount( discountedAmount:
    ( discountClass: string, 
      multipleDiscounts: boolean ) => number ): number
  {
    var discAmount: number;
    ...calculate discAmount...
    return discAmount;
  }
}

Like the recent versions of C#, TypeScript also infers the datatype of a variable from the value to which the variable is initialized. In this example, TypeScript will assume the variable myCust is of CustomerShort:

var myCust= new CustomerShort();
myCust.FullName = "Peter Vogel";

Like C#, you can declare variables using an interface and then set the variable to an object that implements that interface:

var cs: ICustomerShort;
cs = new CustomerShort();
cs.Id = 11;
cs.FullName = "Peter Vogel";

Finally, you can also use type parameters (which look suspiciously like generics in C#) to let the invoking code specify the data type to be used. This example lets the code that creates the class set the datatype of the Id property:

class CustomerTyped<T>
{
  Id: T;
}

This code sets the datatype of the Id property to a string before using it:

var cst: CustomerTyped<string>;
cst = new CustomerTyped<string>();
cst.Id = "A123";

To isolate classes, interfaces and other public members and avoid name collisions, you can declare these constructs inside modules much like C# namespaces. You’ll have to flag those items you want to make available to other modules with the export keyword. The module in Figure 3 exports two interfaces and a class.

Figure 3 Export Two Interfaces and One Class

module TypeScriptSample
{
  export interface ICustomerDTO
  {
    Id: number;
  }
  export interface ICustomerShort extends ICustomerDTO
  {
    FullName: string;
  }
  export class CustomerShort implements ICustomerShort
  {
    Id: number;
    FullName: string;
  }

To use the exported components, you can prefix the component name with the module name as in this example:

var cs: TypeScriptSample.CustomerShort;

Or you can use the TypeScript import keyword to establish a shortcut to the module:

import tss = TypeScriptSample;
...
var cs:tss.CustomerShort;

TypeScript Is Flexible About Data Typing

All this should look familiar if you’re a C# programmer, except perhaps the reversal of variable declarations (variable name first, data type second) and object literals. However, virtually all data typing in TypeScript is optional. The specification describes the data types as “annotations.” If you omit data types (and TypeScript doesn’t infer the data type), data types default to the any type.

TypeScript doesn’t require strict datatype matching, either. TypeScript uses what the specification calls “structural subtyping” to determine compatibility. This is similar to what’s often called “duck typing.” In TypeScript, two classes are considered identical if they have members with the same types. For example, here’s a Customer­Short class that implements an interface called ICustomerShort:

interface ICustomerShort
{
  Id: number;
  FullName: string;
}
class CustomerShort implements ICustomerShort
{
  Id: number;
  FullName: string;
}

Here’s a class called CustomerDeviant that looks similar to my CustomerShort class:

class CustomerDeviant
{
  Id: number;
  FullName: string;
}

Thanks to structural subtyping, I can use CustomerDevient with variables defined with my CustomerShort class or ICustomerShort interface. These examples use CustomerDeviant interchangeably with variables declared as CustomerShort or ICustomerShort:

var cs: CustomerShort;
cs = new CustomerDeviant
cs.Id = 11;
var csi: ICustomerShort;
csi = new CustomerDeviant
csi.FullName = "Peter Vogel";

This flexibility lets you assign TypeScript object literals to variables declared as classes or interfaces, provided they’re structurally compatible, as they are here:

var cs: CustomerShort;
cs = {Id: 2,
      FullName: "Peter Vogel"
     }
var csi: ICustomerShort;
csi = {Id: 2,
       FullName: "Peter Vogel"
      }

This leads into TypeScript-specific features around apparent types, supertypes and subtypes leading to the general issue of assignability, which I’ll skip here. Those features would allow CustomerDeviant, for example, to have members that aren’t present in CustomerShort without causing my sample code to fail.

TypeScript Has Class

The TypeScript specification refers to the language as implementing “the class pattern [using] prototype chains to implement many variations on object-oriented inheritance mechanisms.” In practice, it means TypeScript isn’t only data-typed, but effectively object-oriented.

In the same way that a C# interface can inherit from a base interface, a TypeScript interface can extend another interface—even if that other interface is defined in a different module. This example extends the ICustomerShort interface to create a new interface called ICustomerLong:

interface ICustomerShort
{
  Id: number;
}
interface ICustomerLong extends ICustomerShort
{
  FullName: string;
}

The ICustomerLong interface will have two members: FullName and Id. In the merged interface, the members from the interface appear first. Therefore, my ICustomerLong interface is equivalent to this interface:

interface ICustomerLongPseudo
{
  FullName: string;
  Id: number;
}

A class that implements ICustomerLong would need both properties:

class CustomerLong implements ICustomerLong
{
  Id: number;
  FullName: string;
}

Classes can extend other classes in the same way one interface can extend another. The class in Figure 4 extends CustomerShort and adds a new property to the definition. It uses explicit getters and setters to define the properties (although not in a particularly useful way).

Figure 4 Properties Defined with Getters and Setters

class CustomerShort
{
  Id: number;
}
class CustomerLong extends CustomerLong
{
  private id: number;
  private fullName: string;
  get Id(): number
  {
    return this.id
  }
  set Id( value: number )
  {
    this.id = value;
  }
  get FullName(): string
  {
    return this.fullName;
  }
  set FullName( value: string )
  {
    this.fullName = value;
  }
}

TypeScript enforces the best practice of accessing internal fields (like id and fullName) through a reference to the class (this). Classes can also have constructor functions that include a feature C# has just adopted: automatic definition of fields. The constructor function in a TypeScript class must be named constructor and its public parameters are automatically defined as properties and initialized from the values passed to them. In this example, the constructor accepts a single parameter called Company of type string:

export class CustomerShort implements ICustomerShort
{
  constructor(public Company: string)
  {       }

Because the Company parameter is defined as public, the class also gets a public property called Company initialized from the value passed to the constructor. Thanks to that feature, the variable comp will be set to “PH&VIS,” as in this example:

var css: CustomerShort;
css = new CustomerShort( "PH&VIS" );
var comp = css.Company;

Declaring a constructor’s parameter as private creates an internal property it can only be accessed from code inside members of the class through the keyword this. If the parameter isn’t declared as public or private, no property is generated.

Your class must have a constructor. As in C#, if you don’t provide one, one will be provided for you. If your class extends another class, any constructor you create must include a call to super. This calls the constructor on the class it’s extending. This example includes a constructor with a super call that provides parameters to the base class’ constructor:

class MyBaseClass
{
  constructor(public x: number, public y: number ) { }   
}
class MyDerivedClass extends MyBaseClass
{
  constructor()
  {
    super(2,1);
  }
}

TypeScript Inherits Differently

Again, this will all look familiar to you if you’re a C# programmer, except for some funny keywords (extends). But, again, extending a class or an interface isn’t quite the same thing as the inheritance mechanisms in C#. The TypeScript specification uses the usual terms for the class being extended (“base class”) and the class that extends it (“derived class”). However, the specification refers to a class’ “heritage specification,” for example, instead of using the word “inheritance.”

To begin with, TypeScript has fewer options than C# when it comes to defining base classes. You can’t declare the class or members as non-overrideable, abstract or virtual (though interfaces provide much of the functionality that a virtual base class provides).

There’s no way to prevent some members from not being inherited. A derived class inherits all members of the base class, including public and private members (all public members of the base class are overrideable while private members are not). To override a public member, simply define a member in the derived class with the same signature. While you can use the super keyword to access a public method from a derived class, you can’t access a property in the base class using super (though you can override the property).

TypeScript lets you augment an interface by simply declaring an interface with an identical name and new members. This lets you extend existing JavaScript code without creating a new named type. The example in Figure 5 defines the ICustomerMerge interface through two separate interface definitions and then implements the interface in a class.

Figure 5 The ICustomerMerge Interface Defined Through Two Interface Definitions

interface ICustomerMerge
{
  MiddleName: string;
}
interface ICustomerMerge
{
  Id: number;
}
class CustomerMerge implements ICustomerMerge
{
  Id: number;
  MiddleName: string;
}

Classes can also extend other classes, but not interfaces. In TypeScript, interfaces can also extend classes, but only in a way that involves inheritance. When an interface extends a class, the interface includes all class members (public and private), but without the class’ implementations. In Figure 6, the ICustomer interface will have the private member id, public member Id and the public member MiddleName.

Figure 6 An Extended Class with All Members

class Customer
{
  private id: number;
  get Id(): number
  {
    return this.id
  }
  set Id( value: number )
  {
    this.id = value;
  }
}
interface ICustomer extends Customer
{
  MiddleName: string;
}

The ICustomer interface has a significant restriction—you can only use it with classes that extend the same class the interface extended (in this case, that’s the Customer class). TypeScript requires that you include private members in the interface to be inherited from the class that the interface extends, instead of being reimplemented in the derived class. A new class that uses the ICustomer interface would need, for example, to provide an implementation for MiddleName (because it’s only specified in the interface). The developer using ICustomer could choose to either inherit or override public methods from the Customer class, but wouldn’t be able to override the private id member.

This example shows a class (called NewCustomer) that implements the ICustomer interface and extends the Customer class as required. In this example, NewCustomer inherits the implementation of Id from Customer and provides an implementation for MiddleName:

class NewCustomer extends Customer implements ICustomer
{
  MiddleName: string;
}

This combination of interfaces, classes, implementation and exten­sion provides a controlled way for classes you define to extend classes defined in other object models (for more details, check out section 7.3 of the language specification, “Interfaces Extending Classes”). Coupled with the ability of TypeScript to use information about other JavaScript libraries, it lets you write TypeScript code that works with the objects defined in those libraries.

TypeScript Knows About Your Libraries

Besides knowing about the classes and interfaces defined in your application, you can provide TypeScript with information about other object libraries. That’s handled through the TypeScript declare keyword. This creates what the specification calls “ambient declarations.” You many never have to use the declare keyword yourself because you can find definition files for most JavaScript libraries on the DefinitelyTyped site at definitelytyped.org. Through these definition files, TypeScript can effectively “read the documentation” about the libraries with which you need to work.

“Reading the documentation,” of course, means you get data-­typed IntelliSense support and compile-time checking when using the objects that make up the library. It also lets TypeScript, under certain circumstances, infer the type of a variable from the context in which it’s used. Thanks to the lib.d.ts definition file included with TypeScript, TypeScript assumes the variable anchor is of type HTMLAnchorElement in the following code:

var anchor = document.createElement( "a" );

The definition file specifies that’s the result returned by the createElement method when the method is passed the string “a.” Knowing anchor is an HTMLAnchorElement means TypeScript knows the anchor variable will support, for example, the addEvent­Listener method.

The TypeScript data type inference also works with parameter types. For example, the addEventListener method accepts two parameters. The second is a function in which addEventListener passes an object of type PointerEvent. TypeScript knows that and supports accessing the cancelBubble property of the PointerEvent class within the function:

span.addEventListener("pointerenter", function ( e )
{
  e.cancelBubble = true;
}

In the same way that lib.d.ts provides information about the HTML DOM, the definition files for other JavaScript provide similar functionality. After adding the backbone.d.ts file to my project, for example, I can declare a class that extends the Backbone Model class and implements my own interface with code like this:

class CustomerShort extends bb.Model implements ICustomerShort
{
}

If you’re interested in details on how to use TypeScript with Backbone and Knockout, check out my Practical TypeScript columns at bit.ly/1BRh8NJ. In the new year, I’ll be looking at the details of using TypeScript with Angular.

There’s more to TypeScript than you see here. TypeScript version 1.3 is slated to include union datatypes (to support, for example, functions that return a list of specific types) and tuples. The TypeScript team is working with other teams applying data typing to JavaScript (Flow and Angular) to ensure TypeScript will work with as broad a range of JavaScript libraries as possible.

If you need to do something that JavaScript supports and TypeScript won’t let you do, you can always integrate your JavaScript code because TypeScript is a superset of JavaScript. So the question remains—which of these languages would you prefer to use to write your client-side code?


Peter Vogel is a principal with PH&V Information Services, specializing in Web development with expertise in SOA, client-side development and UI design. PH&V clients include the Canadian Imperial Bank of Commerce, Volvo and Microsoft. He also teaches and writes courses for Learning Tree International and writes the Practical .NET column for VisualStudioMagazine.com.

Thanks to the following Microsoft technical expert for reviewing this article: Ryan Cavanaugh