Practical .NET
What's New in TypeScript 0.9
The latest version of TypeScript adds generics, but there's more in the package than that. Here's an in-depth look at what's new.
Since this column launch, I've been using it to work through building an application with TypeScript (the application has been my excuse to explore the language and its tooling). However, the latest version of TypeScript (0.9) dropped last month, so this column takes a break from the application to look at what's new in the language.
From a functional point of view, the short answer to "What's new?" is "Not many things"; but that's because a lot of the changes are under the hood. The compiler has been re-engineered to perform better during the incremental compiles that occur while you're entering code in the editor (that gives you better feedback at "typing in" time). Error catching and reporting in the compiler is also improved. The compiler also does a better job of passing type information around, so that a variable that has its datatype set through the value to which the variable is set is more likely to get typed correctly.
Related to these improvements in datatyping is overloading on constants. In some contexts, TypeScript now uses the values of string parameters to determine data types. For instance, a string set to "blur" will, when passed to the addEventListener method, be assumed to be referring to the blur event object. The benefit here is better IntelliSense support and compile-time type checking, since the compiler assumes you're working with a specific object rather than a string.
If you're using some module loading system (for instance, AMD or CommonJS), TypeScript now allows you to specify what can and can't be loaded with the Export = syntax.
And, while there aren't many new programming features, one of them is a biggie: TypeScript now supports generics. Before getting to generics, though, I'll clear the other two items out of the way: Enums and declaration merging.
Enums
TypeScript 0.9 finalizes the syntax for enums. If you want to create a list of enumerated values for kinds of customers, defined in one module to be used in other modules, you write code like this:
module AdventureWorksEntities {
export enum CustomersType {
Deadbeat,
Standard,
Premium
}
You can then declare variables using your enumerated type. Here's my ICustomer interface with getters and setters for a CustomersType property:
export interface ICustomer
{
setId(ID: number);
getId(): number;
…properties omitted…
getcustomerType(): CustomersType;
setcustomerType(CustType: CustomersType);
}
The Customer class that implements the interface (and accepts an enumerated value in its constructor) would look like this:
export class Customer implements ICustomer
{
constructor(public Id: number,
public FirstName: string,
public LastName: string,
public CustomerType: CustomersType) {}
}
public getId() {
return this.Id;
}
public setId(Id: number) {
this.Id = Id;
}
…properties omitted…
public getcustomerType() {
return this.CustomerType;
}
public setcustomerType(CustType:
AdventureWorksEntities.CustomersType) {
this.CustomerType = CustType;
}
}
Using that class and passing an enumerated value to the constructor and setting/retrieving the property looks like this:
var cust: AdventureWorksEntities.ICustomer;
var currentType: AdventureWorksEntities.CustomersType;
cust = new AdventureWorksEntities.Customer(
123, "Peter", "Vogel",
AdventureWorksEntities.CustomersType.Premium);
cust.setcustomerType(AdventureWorksEntities.CustomersType.Deadbeat);
currentType = cust.getcustomerType();
In other words, enums work pretty much the way you'd expect them to. That's not quite as true for the next new feature.
Extending Types
When I started working with TypeScript, I was concerned I'd lose the language features I'd come to appreciate. For instance, one of the things that drives programmers new to JavaScript nuts is the language's willingness to create new members on classes when all you did was make a typing mistake. For instance, if you might have a property on your Customer class named FirstName but you might inadvertently type this:
var cust;
cust = new Customer();
cust.FrstNme;
JavaScript will obligingly add a member named FrstNme to your object. While this drives object-oriented programmers crazy, experienced JavaScript developers regard this as one of the benefits of working in a prototype-based language. Unfortunately for that point of view, since TypeScript is an object-oriented language, TypeScript flags the cust.FrstNme line as an error and refuses to compile your code.
The good news for the prototype-oriented developers is that TypeScript 0.9 allows you to extend previously defined types in a controlled way: you create a module with a name that matches an existing class that holds the members you want to add to the original class. The members in the module will be merged with the class at compile time (Microsoft refers to this as "declaration merging").
For instance, rather than rewriting my Customer class to include the CustomerType property, I could have extended the class with a similarly named module. My Customer class initial Customer class might look like this:
export class Customer implements ICustomer
{
constructor(public Id: number,
public FirstName: string,
public LastName: string) {}
…members…
}
I could then add my new property by declaring a module called Customer whose declarations will be merged with my Customer class:
module Customer {
export var CustomerType: CustomersType;
}
You can even use the merged members within the original class. This constructor for the Customer class, for instance, sets a default value for the new CustomerType member:
export class Customer implements ICustomer
{
constructor(public Id: number,
public FirstName: string,
public LastName: string)
{
this.CustomerType = CustomersType.Standard;
}
This isn't a perfect implementation, however: although the compiler will tell you if you attempt to use a member that isn't defined in a class or a module, these extended members don't appear in the IntelliSense list for the class. More critically, it appears the declarations that you're merging must all be declared in the same module as the class (i.e., you can't extend a class declared in some other module). If that's the case, it seems to me to be a significant limitation on the usefulness of the feature. The documentation explicitly says that this feature is limited to properties and, in my tests, I wasn't able to add new functions to a class.
Generics
But the big news is TypeScript 0.9's support for generics. This allows me, for instance, to write a class where I will specify data types when I instantiate the class. This example, for instance, puts off declaring the datatype for my CustomerType member, and just uses the placeholder T to mark where the type should be used:
export class TypedCustomer<T>
{
constructor(public CustomerType: T){}
public setCustomerType(CustomerType:T)
{
this.CustomerType = CustomerType;
}
public getCustomerType():T
{
return this.CustomerType;
}
}
Now, when I instantiate the class I can specify that the T placeholder is to be replaced with my enum class, CustomersType. After that, I can just use the property as if it had been declared with the datatype:
var currentType: AdventureWorksEntities.CustomersType;
var tcust: AdventureWorksEntities.TypedCustomer
<AdventureWorksEntities.CustomersType>;
tcust = new AdventureWorksEntities.TypedCustomer(
AdventureWorksEntities.CustomersType.Premium);
tcust.CustomerType = AdventureWorksEntities.CustomersType.Premium;
currentType = tcust.CustomerType;
You can also declare a JavaScript Array as a generic, which allows the compiler to check that you're not putting anything in the array except your specified datatype. This example declares an array as a collection of Customer objects and puts a Customer object in it:
var cust: AdventureWorksEntities.Customer;
cust = new AdventureWorksEntities.Customer(123, "Peter", "Vogel")
var custs: Array<Customer> = [cust];
As you might expect with an upgrade, I had some problems: Two, to be exact. Chutzpah, my test runner tool, doesn't understand generics and stopped working. Chutzpa's author is working on supporting TypeScript 0.9 and may have addressed the issue by the time you read this.
The only other thing that broke in the code I've written so far were that the semicolons I put at the end of my property declarations: They now generated compile-time errors. I deleted the excess semicolon and everything worked swell, which is always a pleasant surprise when working with beta code.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.