TypeScript makes us .NET developers comfortable with JavaScript. When you come from c#, coding JavaScript feels like walking on a tightrope without a safety net. With TypeScript comes a typesystem, a compiler and editors with intellisense. Also, the language syntax looks more familiar. This makes TypeScript popular amongst .NET developers doing web.

I’m torn. I like coding a TypeScript, but as an exception to the rule of .NET developers I actually also like coding in JavaScript.

The problem I have with TypeScript in web projects is that I often get the feeling, it was chosen because the developers prefer to avoid having to understand JavaScript and want to code the web-frontend in a language they are comfortable with.

Rather than trying to avoid properly learning JavaScript, I think anybody doing front end web should embrace it and learn to love it. Yes, really!

First and foremost, I feel that you should learn to use the right tool for the job, rather than trying to retrofit something you are comfortable with. But beside from my feeling of what’s right, I also think that you ought to consider the following: The browser executes only the resulting JavaScript and has not idea about the TypeScript behind it. So to make it work and especially to debug the resulting JavaScript you still have to understand all the details of JavaScript in a browser. You have not removed complexity, in fact you have added complexity, by adding another language, a compiler and an extra development workflow.

The following is an illustration of why using TypeScript can make your code more difficult to understand and debug, which is directly inspired (but simplified) by a real issue faced recently on my current project.

The calculator

The source

The following is the (trivial, I know) source code for a simple calculator written in TypeScript:

function add(arg1: number, arg2: number): number{ return arg1 + arg2; } function subtract(arg1: number, arg2: number): number{ return arg1 - arg2; } function multiply(arg1: number, arg2: number): number{ return arg1 * arg2; } function divide(arg1: number, arg2: number): number{ return arg1 / arg2; }

Pretty straightforward.

The unit test

Just to be sure, we’ll use jasmin to add unit testing:

///<reference path="../calculator.ts" /> ///<reference path="jasmine.d.ts" /> it("Can add", function(){ expect(add(1,4)).toEqual(5); }); it("Can subtract", function(){ expect(subtract(1,4)).toEqual(-3); }); it("Can multiply", function(){ expect(multiply(1,4)).toEqual(4); }); it("Can divide", function(){ expect(divide(1,4)).toEqual(0.25); }); //it("Doesn't accept strings", function(){ // expect(divide("1", 2)).toEqual(0.5); //})

The reason the last test is commented out is because TypeScript causes a compilation error:

test/calculator_test.ts(17,19): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

So we have no type issues to worry about. As expected the tests pass. After all, there is a limit to how much we can screw up something this simple.

The interface

So the code for the calculator works. Now all we have to do is use it in a web-interface:

<html> <style>label{display: block;}</style> <script> function getValue(id) { return document.getElementById(id).value; } function setResult(result){ document.getElementById("result").innerHTML = result; } </script> <script src="calculator.js"></script> <label> Argument 1: <input id="arg1" type="number" /> </label> <label> Argument 2: <input id="arg2" type="number" /> </label> <input type="button" value="Add" onclick="setResult(add(getValue('arg1'), getValue('arg2')))"></input> <input type="button" value="Subtract" onclick="setResult(subtract(getValue('arg1'), getValue('arg2')))"></input> <input type="button" value="Multiply" onclick="setResult(multiply(getValue('arg1'), getValue('arg2')))"></input> <input type="button" value="Divide" onclick="setResult(divide(getValue('arg1'), getValue('arg2')))"></input> <label id="result"></label> </html>

In the html, we wire up input fields and result-label to use the amazing calculator-code.

This produces the following very simple interface and as one can see “divide” works as expected:

We have used <input type=”number” /> in the markup and we have used TypeScript to write the calculator source code. We even unit tested the source doing the mathematical operations, so everything ought to work.

The problem

Nothing could be further from the truth. A quick press of the “Add” button reveals some strange behavior:

We have managed to create a calculator, which can’t correctly add numbers. We used a compiled, typesafe programming language and even added unit tests to make sure everything works like it should. But it doesn’t.

The cause

To understand this issue, we have to look past the TypeScript code and understand the generated JavaScript:

function add(arg1, arg2) { return arg1 + arg2; } function subtract(arg1, arg2) { return arg1 - arg2; } function multiply(arg1, arg2) { return arg1 * arg2; } function divide(arg1, arg2) { return arg1 / arg2; }

The JavaScript doesn’t know, that arg1 and arg2 are supposed to be numbers, so they simply accept whatever

document.getElementById(id).value

returns, which is a string regardless of type=”number” in the markup! And since the connection between the input fields and the calculator code takes place in JavaScript, the TypeScript compiler doesn’t help us at all.

wtfjs

The strange thing is then… Why is it that only “Add” fails, when the three other arithmetic operations work?

Well, this is because of JavaScripts typesystem, which one must understand to understand why our TypeScript code acts so strangely.

The ‘-‘, ‘*’ and ‘/’ operators in JavaScript only works with number types. So when we feed strings into those functions, JavaScript will attempt to convert those strings to numbers and then do the math. Like this:

However, ‘+’ can also be used to concatenate strings:

So when we pass to strings into the “add” function, it will concatenate the strings instead of adding the numbers.

The fact that we declared a TypeScript function that takes only numbers and returns a number

function add(arg1: number, arg2: number): number

doesn’t help, because that the end of the day, the browser only knows about the JavaScript.

The solution

The solution is to stealth the “add” function by ensuring the input arguments are in fact converted to numbers by the use of the unary plus operator from JavaScript:

function add(arg1: number, arg2: number): number{ return +arg1 + +arg2; }

Shouldn’t be necessary, right? Also, since the TypeScript compiler prevents us from passing strings into the function, we can’t even unit test the function with string input to make sure the type conversion works like it should.

Other fun versions…

1 - "-3" // 4 1 - -"3" // 4 1 + "-3" // "1-3" 1 + -"3" // -2

Another example – angular $stateparams

In another example I encountered, I thought I was clever, to define a TypeScript interface to ensure a correct declaration of $stateparams and help with renaming, refactoring etc:

interface ViewModelStateParams{ foo: number, bar: string } class MyViewModel{ private _foo: number; private _bar: string; constructor (params: ViewModelStateParams){ this._foo = params.foo; this._bar = params.bar; } }

and to go to the state I can use:

$state.go("mystate", <ViewModelStateParams>{foo: 1, bar: "bar", d:3})

The problem here is, that even though I create a stateparams object with a number property, the constructor gets called with an object where all properties are strings. Because I used TypeScript, I thought the constructor received an object with certain types into the constructor, when in fact the underlying transport mechanism and JavaScript mechanics changed those types at runtime. And again, the compiler doesn’t help me at all, on the contrary it caused me to be very confused about why my code wasn’t working.

In conclusion

To sum up: using TypeScript in a web project, doesn’t mean that you don’t have to understand and deal with all the “weird” details of JavaScript. On the contrary, you will be faced with JavaScript weirdness in places, where you wouldn’t have imagined it possible.