In my previous article for CODE Magazine (http://www.codemag.com/Article/1509071), I outlined numerous interesting challenges that you deal with when writing JavaScript. JavaScript is a very popular language, and you need to learn it; you need to be very good at it. If anything, the last article made it amply clear that writing reliable and bug-free JavaScript is very difficult. The language has too many idiosyncrasies and loose ends to allow you to write reliable code for every circumstance. And although I think I did a pretty good job of outlining all the mistakes you can make in JavaScript, the reality is, when writing code - especially in the heat of a deadline - it's all too easy to make mistakes.
Find additional: JavaScript Articles
The fact remains: You need to be excellent at JavaScript. You need to find ways to write reliable code using JavaScript. Here are three steps to JavaScript heaven:
- Use transpilers. My choice is TypeScript.
- Use TDD.
- Use good design patterns, such as MVC, and frameworks that encourage that, such as AngularJS.
In this article, I'll focus on TypeScript. In subsequent articles, I'll focus on TDD and AngularJS. There are plenty of tutorials available and articles teaching you the basics of these three steps, so I'll assume that you're familiar with these technologies. Instead of focusing on “What is TypeScript,” I'll instead focus on “Why TypeScript is necessary.”
If you've never tried TypeScript, I encourage you to watch this video on Channel9 https://channel9.msdn.com/events/Build/2014/3-576. In that video, Anders Hejlsberg talks about the basic architecture, syntax, and some applications of TypeScript.
Beyond what Anders says in that video, here are my reasons why TypeScript is amazing!
A Superset of JavaScript
TypeScript is a superset of JavaScript. This means that introducing TypeScript to your current JavaScript code is easy - just rename the .js
file to .ts, and you're good to go. Of course, by simply renaming a file you, don't get the full power of TypeScript. But by simply renaming the file to .ts and continuing to write JavaScript like you've always done, you begin to start getting some of the advantages of Typescript. These advantages are, of course, dependent upon the IDE you use, but for the purposes of this article, I'll talk about Visual Studio 2015 or Visual Studio Code.
For instance, consider the very simple add
function, as shown below,
function add(a, b) {
return a + b;
}
As you can tell, this is plain JavaScript. Because you renamed the file to .ts, the IDE begins to understand the code better, and allows you to refactor the code easily. Just right click on the “add” text, and you'll see a menu, as shown in Figure 1.
You can easily rename a variable or function name. You can also peek or go to the definition. You can find all references. In other words, you have the power of a higher-level language, such as C#. If this were plain JavaScript, you'd be stuck doing error-prone string replacement. Try out the same code in a JavaScript file for a trip back to the 1990s.
Try out the same code in a JavaScript file for a trip back to the 1990s.
Additionally, the IDE also warns you if you're not passing in the parameters properly. For instance, if your add
function expects two parameters but you're accidentally passing in only one, you get a nice red squiggly line, as shown in Figure 2.
Not only that, the IDE is able to judge the variable types, even if you don't specify their data types. In doing so, it'll then help you avoid common mistakes that are otherwise syntactically perfect JavaScript code. This can be seen in Figure 3.
Static Typing
The lack of static typing is JavaScript's strength and its weakness. It makes the language very flexible, but also very error-prone. TypeScript introduces the concept of static typing to JavaScript. It's as simple as this, when you don't assign a variable's data type by typing var variablename, TypeScript tries its best to infer its data type using whatever the variable is assigned to. If it's unable to determine the data type, it assigns it the any data type.
You can go one step further, and assign specific data types to your variables. The data types can be Boolean, Number, String, Array, Enum, Void, or Any. You can also assign an object type as a data type.
For instance, I've slightly modified the add
function as below,
function add(a:number, b:number) {
return a + b;
}
Now, if you accidentally try to add two strings - which is a very common mistake given that the concatenation operator works on both strings and numbers - you'll get a helpful error, as shown in Figure 4.
In fact, Visual Studio gives IntelliSense hints, as can be seen in Figure 5.
For the most commonly used datatypes in HTML, TypeScript implements interfaces, allowing you to strongly type the selected HTML element types, as can be seen in Figure 6.
If you're using a non-HTML JavaScript engine, such as V8 running in NodeJS, you can add definition files to add support for known types specific to a platform. More on that later.
The advantage of being able to strongly type HTML elements (as shown in Figure 6), is that now you get rich coding help when authoring TypeScript. For instance, the HTMLCanvasElement
is somewhat new, and I didn't realize that you could easily convert the canvas data into a DataURL
and stick on an image very easily. Fortunately, TypeScript IntelliSense teaches you as you go along, as shown in Figure 7.
Features from the Future
JavaScript sucks, but it's is getting better. There's ES6, which attempts to solve many of the issues I outlined in my WTF.js article last time in CODE Magazine. The problem is that the browser where your code runs may not support ES6, or even worse, not all of ES6 yet.
TypeScript to the rescue! You can write your code using everything ES6 offers, and even some of what ES7 will offer at some point in the future. The TypeScript transpiler converts your code to ES5 or even ES3 if you ask it to. This way, you can use the features of ES6 today and write reliable code right now.
There are many other examples. I suggest visiting http://es6-features.org/ and checking out everything ES6 offers. Many of those offerings are usable today in JavaScript. As a simple example, let me pick just one of those features, called Arrow Functions. Arrow Functions is a shorthand for writing full functions, much like Lambda expressions in C#.
For instance, examine the code below that is ES6 code, but that can be written in a TypeScript file:
var odds = evens.map(v => v + 1);
Those of us familiar with Lambda Expressions immediately understand what that code attempts to do. Here's the automatically generated JavaScript equivalent of the above code:
var odds = evens.map(
function (v) {
return v + 1; });
If you remember from my WTFjs article, I mentioned that the this
keyword is extremely confusing. That's because the value of this
changes based on context. The good news is that lambda expressions, or Arrow Functions, automatically assign the right value to this
so you don't have to save this
into that
.
I could spend all day talking about ES6 and how it's going to solve all of the challenging issues that humanity faces today. Each example is better than the next. I'll let you try them in ES6 in your own time. Before I leave this topic, though, let me show one other example.
In my WTF.js article, I explained why globals are evil, and how JavaScript lets you create accidental globals super easily. Even when you do remember to put in the var
keyword, you still have limited control over where the variable is scoped. Loops are especially problematic. I wish there were a block scope restricted by curly brace symbols {} in JavaScript, like in almost any other similar language.
ES6 introduces a keyword called let
that does exactly that. For instance, consider the code below:
var i = 10;
for (let i = 1; i < 10; i++) {
// do something useful
}
The above is ES6 code, but the let
keyword ensures that the inner i
variable in the for
loop doesn't interfere with the same-named variable outside the loop. TypeScript allows you to write exactly the same code, and run it and take advantage of it in browsers that don't support ES6 yet. TypeScript converts the above code into the following:
var i = 10;
for (var i_1 = 1; i_1 < 10; i_1++) {
}
I encourage you to type out ES6 in TypeScript. You'll see that the majority of them are usable today. Even some ES7 features are supported today.
Compatibility with Browsers
I hate browser proliferation. Yes, I know: If there were no Firefox, we'd still be using IE6. But proliferation makes my job as a Web developer so much harder. I hate browser differences so much! So many times, I've written code and tested it in IE, only to find that some smart guy tried it in Firefox. When I fix it in Firefox, it breaks in Chrome. I fix it in chrome and it breaks in IE. Give me a break!
TypeScript helps fix that problem too. You just write in TypeScript, the one language common across all browsers. You ask the TypeScript compiler to convert it to ES3, ES5, or the most commonly used option, commonJS. This always ensures that the correct cross-browser compatible JavaScript is emitted, no matter what you write on the other side.
It's so good to focus on a single language set finally! It makes me productive, and I think I'll stick with it.
IntelliSense
IntelliSense is a big deal. I've touched upon this in the “Static Typing” section above. It gets even better! You could write interfaces or classes, for instance, as shown here:
interface animal {
animalName: string;
}
Once you've declared interfaces and classes, and have strongly typed your variables properly, you start getting rich intelliSense, making your code a lot more meaningful, as can be seen in Figure 8.
You can see how powerful this can be, especially when authoring large libraries and complex interdependent JavaScript/TypeScript code. It gets even better than that. Much better!
These days, you almost never write JavaScript from scratch. You always stand on someone else's shoulders by using libraries and frameworks, such as jQuery and AngularJS. For nearly every library you care about, the community has written .d.ts or definitely typed files, that give you IntelliSense for those third-party libraries.
For instance, for AngularJS, I'd simply add a line at the top of my code as shown here:
/// <reference path="/dts/angularjs/angular.d.ts" />
By doing so, I get rich IntelliSense for all of AngularJS. For instance, as can be seen in Figure 9, I get to know what interface type “angular” is the variable of. If you were wondering why Figure 9 looks different, it's because it's from Visual Studio Code, the freebie cross-platform code editor that Microsoft introduced recently.
Another such example is the forEach
method. It'd be nice to be able to iterate over a JavaScript array. AngularJS thinks so, jQuery agrees, and so does ES6. Except that the original spec of JavaScript didn't think iterating over an array would be that important. Heh.
Both jQuery and Angular give us a forEach
method. Unfortunately, their parameter orders are reversed. This alone causes a lot of confusion and errors because frequently, you end up using jQuery and Angular together.
Fortunately, TypeScript tells you exactly what to expect, as can be seen in Figure 10.
There are many other such helpful hints across the IDE such as hints, tool tips, IntelliSense etc. As someone used to higher-level languages, these will be second nature to you. You'll find writing JavaScript just as much fun as writing C#.
Rich Community Support
The question, of course, is, where do you get the .d.ts
files for your project? The answer is that for code you write, the TypeScript compiler generates the d.ts
files for you. For the libraries you use, you can simply download the d.ts
files from www.definitelytyped.com.
All of those files that you can download for absolutely free are quite good. I'll never understand how open source is so good, but I'll take it. For instance, I was writing AngularJS code and wanted to write a constant on my module when I got the very helpful IntelliSense help text seen in Figure 11.
Yes, before you ask, it works on a Mac too. In fact, the Visual Studio Code IntelliSense tooltip is slightly better formatted. It's not bad for a beta product.
One thing I must caution you about, however, is that TypeScript and the IDEs that make use of it don't understand the difference between standard third-party libraries and your code. For instance, you're not supposed to rename the module.constant
in Figure 11 to module.donkeykong
. That “constant” function is declared in a standard third-party library called AngularJS.
Both Visual Studio and Visual Studio Code let you rename it without any warning bells or sirens going off. Ouch!
I wish that the IDEs supported concepts of blackboxing framework files. Until that happens, here's a best practice I'd like to share with you. The d.ts
files should reside in a folder and simply mark that folder as read only. Then, you'll never accidentally change them. In your master git repo or SVN folder, ensure that most developers don't have write-access to that particular folder. This way, with the combination of TypeScript and source control, you'll never accidentally step on this landmine.
Object-Oriented Features
Okay, raiseYourHand()
; if you: ILikeObjectOrientedCode
. I thought so!
JavaScript isn't object oriented. It's weird-oriented. It likes prototypes, which are sort of like object-oriented and inheritance, but not quite. In fact, prototypal inheritance is confusing. If you're a top notch OO developer, prototypal inheritance actually makes it difficult for you to understand what's going in.
JavaScript isn't object oriented. It's weird-oriented.
TypeScript solves that problem too. It brings concepts such as inheritance, interfaces, classes, etc. into JavaScript in the manner in which you'd expect them to behave. For instance, consider this code:
interface foodItem {
foodName: string;
calories?: number;
}
It simply declares an interface, which says that the object implementing this interface must have a required
property of type string called foodName
, and an optional number property called calories
.
This means that I can start writing code like this:
var newFoodItem: foodItem = {
foodName: "celery"
}
TypeScript ensures that every foodItem
at least has a name. And it allows you not to specify calories information. This means that every bit of code relying on the foodItem
data type can be guaranteed not to be null
or have undefined checks on foodName
. This alone is a huge win!
You know this from C#. Interfaces let you establish consistency. But JavaScript is a lot more powerful than C#. In JavaScript, every function is an object, and both can be thought of and used interchangeably.
If you remember from my WTF.js article, I mentioned how much JavaScript arrays suck! TypeScript solves almost every issue with JavaScript arrays. One of those issues, for instance, is that you can stuff anything into an array. There's no way to establish consistency amongst the various objects inside an array.
TypeScript allows you to establish that consistency with just one line of code, as shown here:
interface foodItemsArray extends Array<foodItem>
{
[index: number]: foodItem;
}
Now, if some developer tries to push a non-food item into this array, the IDE catches that error for you. You can see this in Figure 12.
I'm using an advanced OO concept called Generics here. That's another side-benefit of TypeScript.
As I mentioned, JavaScript treats functions as objects, which means that you can do things that you can't do in C#, such as declaring interfaces that define function signatures. For instance, consider the code here:
interface newFoodItem {
(foodName: string, calories: number): foodItem;
}
This is an interface that defines a function signature and a return value type. A possible implementation of this function is shown in Listing 1.
Listing 1: A function that implements an interface
var addFoodItem: newFoodItem =
function (foodName: string, calories: number) {
var newFoodItem: foodItem = {
foodName: foodName,
calories: calories
};
foodItems.push(newFoodItem);
return newFoodItem;
}
There are a number of interesting concepts in play in Listing 1. I've defined a function that implements an interface. Additionally, the array is guaranteed to have only variables matching a certain predefined data type. This means that you don't have to do onerous typechecking or null
and undefined checks. You could, of course, take this further and create a single interface that's both a function and has properties of the function. Just imagine how difficult or error prone it would be to achieve the same in plain old JavaScript!
Object-oriented concepts don't stop at interfaces. There are also classes that have private variables and classes that inherit from each other. There are also static properties and constructors. Sometimes these constructors can also have optional parameters, effectively giving you overloaded constructors.
Now pause and think for a moment: How would you implement all that stuff in the preceding paragraph in plain JavaScript? I'm serious, stop reading, and think through how you would implement this in JavaScript. It's complicated, isn't it? I'm sure you're thinking in terms of prototypes, returning various properties of prototypes, all cleverly placed to mimic
class properties. You're probably also thinking of how you would implement static variables. It's doable, but not easy!
In TypeScript, you simply write it as shown in Listing 2.
Listing 2: Implementing a class with constructurs and statics
class Animal {
static standardSound = "Woof";
animalType: string;
newSound = "";
constructor(animalType?: string, newSound?: string) {
this.animalType = animalType;
this.newSound = newSound;
}
makeSound() {
if (this.animalType == "Dog") {
return Animal.standardSound;
}
else {
return this.newSound;
}
}
}
var dog: Animal = new Animal();
dog.makeSound(); // Woof
var cat: Animal = new Animal("cat", "meeow");
cat.makeSound(); // meeow
Inheritance as done in TypeScript is a lot easier to understand than as done in JavaScript. It only gets worse if you inherit. For instance, if I were to inherit just one level deep by creating a class called WildAnimal
that inherited from Animal
, in TypeScript, it would look like this:
class WildAnimal extends Animal {
habitat:string
}
The JavaScript implementation, on the other hand, is shown in Listing 3.
Listing 3: A complicated and error prone JavaScript implementation
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
__.prototype = b.prototype;
d.prototype = new __();
};
var Animal = (function () {
function Animal(animalType, newSound) {
this.newSound = "";
this.animalType = animalType;
this.newSound = newSound;
}
Animal.prototype.makeSound = function () {
if (this.animalType == "Dog") {
return Animal.standardSound;
}
else {
return this.newSound;
}
};
Animal.standardSound = "Woof";
return Animal;
})();
var WildAnimal = (function (_super) {
__extends(WildAnimal, _super);
function WildAnimal() {
_super.apply(this, arguments);
}
return WildAnimal;
})(Animal);
var dog = new Animal();
dog.makeSound(); // Woof
var cat = new Animal("cat", "meeow");
cat.makeSound(); // meeow
Inheritance and OO concepts are very important. When writing relatively complex code, it's a tool that I find essential. TypeScript puts all this power within my reach.
Modules
Modularizing code is yet another important code organization tool I like to use. Modularization is important because it lets you split your code into multiple files, and each file can expose its interaction points while keeping the internal sausage factory secret.
For instance, consider the code shown in Listing 4.
Listing 4: Splitting your code in modules
module Greetings {
export interface Greet {
sayHello(person: string): string;
}
}
module FormalGreetings {
export class SayFormalGreeting implements Greet {
sayHello(person: string) {
return "Good Morning : " + person;
}
}
}
var formalGreeting: SayFormalGreeting = {};
formalGreeting.sayHello("Sahil");
As you can see, by splitting the code into Modules, I've been able to hide certain parts of my implementation that the usage of my code doesn't need to bother with. What's even cooler is that I could split these modules into multiple files and simply reference them using the ///
TypeScript, in addition, also allows you to load these dependency modules using the syntax shown here:
import greetingsModule = require('Greetings');
You can therefore conditionally load modules by surrounding this import statement inside if
blocks. You can't do that in compiled languages.
Function Parameters
I've touched upon the power that TypeScript brings to function parameters earlier in this article but it deserves its own section. Earlier in this article, I mentioned a simple add
function that adds two numbers. I'd mentioned it because they're statically typed: you can't pass in strings.
What if I did want to pass in strings, and get a concatenated string back?
What if I wanted to pass in three numbers instead of two?
What if the third parameter had a default value, for when I forgot to supply a value on call?
What if there were an unknown number of parameters?
Although all of the above are achievable in JavaScript, you have to write a lot of code to achieve all of them without any errors. TypeScript makes all of this a lot easier. For instance, consider the code shown in Listing 5.
Listing 5: A better and more flexible add method
function add(a: number, b: number, c: number = 12,
...restOfTheNumbers: number[]) {
return a + b + c;
// write logic to add restOfTheNumbers
}
This same function can now be called in multiple different ways, as shown here:
add(1, 2); // returns 3
add(1, 2, 3); // returns 6
add(1, 2, 3, 4, 5); // returns 16
Summary
JavaScript sucks - I think I've made that abundantly clear. Also, you can't avoid JavaScript - I think I've made that abundantly clear as well. Writing complex and reliable JavaScript is possible. In this article, I showed that the first step to this promised land is by using TypeScript.
As amazing as TypeScript is, there's one big disadvantage: It needs to be transpiled. In other words, the code you write isn't the code that runs. You write some code, it gets converted into JavaScript, and that generated JavaScript runs in the browser. This introduces some friction, especially while debugging. It requires you to set up your development environment considering how and when the TypeScript code gets transpiled into JavaScript. Certainly, you don't want this transpilation to happen on the fly in production. These transpiled files are built and shipped ahead of time. This makes TypeScript perhaps not as flexible as JavaScript. Not to mention that hitting a breakpoint in TypeScript requires complex workarounds such as sourcemaps. And let's be honest, not all browsers understand all of that.
Still, given the huge advantages that TypeScript brings, this is a minor downside that I would gladly overlook. In fact, I'd go as far as saying: TypeScript is the best way of writing JavaScript today.
Also, because TypeScript is a superset of JavaScript, there's really no reason not to write TypeScript. In my next article, I'll talk about another fundamental building block of good JavaScript, which is MVC, Angular, TDD etc. TypeScript is the best way to write Angular 2 code. In fact, the alternative of writing it in plain JavaScript is so bad, I'd argue that it's the only way to write Angular 2 code.
That and more in my next article. Until then, happy coding!