Warning! This article is a bit of a deep dive, with lots of source code. Please let me know in the comments if this is useful or whether it is best to talk in generalities without getting into the weeds.
Tip 1 – Chrome Dev Tools and Source Maps
First, part of the reason I have become so attached to TypeScript is that since late 2014 (Chrome 39) SourceMaps have been enabled by default and work really well with TypeScript, there is basically only 1 trick to learn about debugging in Chrome Dev Tools (or Firefox) in SourceMaps and that is that sometimes ‘this’ is ‘_this’. Which is to say, if you look at TypeScript output you will discover that whenever you use an arrow function, it creates a reference to this in _this. See the example silly class below:
and that converts to :
So, when you are debugging in the Chrome Dev tools, and you set a breakpoint in the output function it will look like you can type:
which fails since this is actually bound to the window (global) scope, but if you type
you are all good. This was learned quickly when we developed the Spa app, but it still trips me up every now and then (especially when dealing with the dreaded scoping of this in classes – more on that later) and it frequently trips up our junior devs so I thought I would mention it at the top.
Tip 2 – Set up a good tsconfig.json File
The first time I built a moderately large TypeScript application I omitted creating a tsconfig.json and did everything with command line options called from a gulp file. Do not make this mistake, while it is nice to have the control over what typescript files get compiled through watchers and the other advantages of grunt, you are going to want to tsconfig.json to be able to set various options of how typescript compiles your files. While the default way to specify all the files you are converting is to create a list of files
however, here is the rub with fileGlob (which is hopefully fixed by the time you read this), it is clearly caching the list files because when you add a new file to the ts directory it won’t compile it. My current solution is to change the second glob (“ts/*/.ts”) to point directly to the file and run typescript again (or have WebStorm run typescript on a watcher, see more about WebStorm and TypeScript later in this post). Then change the filesGlob back to “ts/*/.ts” and it will have smartened up.
There are very useful options available to you in the tsconfig.json file:
noEmitOnError - Don’t output the js file until there are no errors in the ts file.
noFallthroughCasesInSwitch - Don’t allow fall throughs on case switches.
target - The language to target, for now you want es5 but es6 should be soon.
A full list of compiler options can be found here.
Tip 3 - Use Typings (definitely typed)
To get proper IntelliSense or any kind of code completion and static type checking in your editor (whether it is WebStorm, Visual Studio Code, or Vim with TypeScript support) you need to install with Typings all the libraries you are using (unless of course you are really creating VanillaJS TypeScript and if you are kudos). It gives you more than code completion, of course, it also tells the TypeScript compiler how you should be using various APIs and you will get warnings if you are using them wrong.
The following should be pretty self-explanatory and this isn’t an article on writing definitely typed syntax but quickly going over it, the DefaultStatic allows selection through a selector string and takes an optional object for the options. The DefaultJquery requires calling from a JQuery object and also takes an optional Object. Of course you can make these much more detailed (you can specify the potential options, you could make the DefaultStatic take an element or a JQuery object, but the intention is pretty clear). Also, note this does require having already installed the jquery.d.ts file through typings.
Tip 4 - Use tslint
Tip 5 - Use an Editor with support for TypeScript
I love WebStorm and all the JetBrains IDE variants, and I would highly recommend it for developing in TypeScript (you won’t even need to write a Gulp/Grunt script it will do almost all of that work for you out of the box) but there are a lot of editors (see Tip 3) that have plugins that work with the TypeScript compiler to make you more productive, and help you automatically detect errors. The built in support for TypeScript and tslint makes me think that it is a no brainer, but if you prefer VS Code (where it works out of the box), or Sublime the plugin is here, Vim plugin, or even ughh, Eclipse plugin make sure that you are getting all the help that the TypeScript Language Server can give you.
Tip 6 - Namespaces remove the need for iife’s
The first thing I do when I convert a js file is turn the iife (immediately invoked function expression, or a Resig as I believe Scott Hanselman used to call them) that surrounds all the code in the file into a namespace. So
Note: If you look at the output in JS you will note that every dot becomes a new iife with variables attached so don’t use Java style namespaces. For example:
Tip 7 - You don’t have to turn everything into classes
The first couple of files I converted I decided that I would convert from our old style classes (we for the most part have our classes built upon functions and have eschewed the speed of prototypical functions for the advantages of private functions and variables – I don’t say that this is the best way to do classes (and it is not the way that TypeScript impliments them under the hood), but it has worked for us in the past. So we had a class like this:
and after renaming it, adding the namespace and bunch of flipping it became this:
Which isn’t a lot of work for small files, but it is a lot of Regex Replace /this.(\w+) = function/public $1/ and Regex Replace /function (\w+)(/private $1(/ and Regex Replace /var (\w+);/private $1;/ and then manaully adding a lot of this references.
Tip 8 - JQuery can be a nemesis
Then I started running into problems, because there are a lot of JQuery calls in our code, and if something calls that anonymous function without correctly setting the scope of this then you are headed into a heap of trouble. For example, suppose we have some Controller that is using jQuery:
We naively convert it to TypeScript
Uncaught TypeError: this.outputName is not a function
So what is going on? Those who look for a minute will quickly realize (and I added a hint, using the jQuery shorthand $(this).val()) that the scope of this is scoped to the jQuery element that was blurred, and not scoped to our EmployeeController class at all. So not only can we not call the prive outputName function, but we are actually setting name on the nameField input element and not changing the name of our user at all. How to fix this? Well, for one, never bind jQuery callbacks to private or public class members (quick note, there is really no difference in typescript between private and public class members and the only thing stopping you from calling private members of TypeScript classes is the compiler will complain). Also, you are going to ween yourself off of the this scope in jQuery – it’s confusing and leads to more unintentional bugs than you probably realize. And you are going to have to use an arrow function. Let’s look at our fixed class:
Now everything works. See the updated fiddle. Now you might not think that this will be a problem to find, but if you are using a lot of these you will get burned missing a few (at least I did).
Tip 9 - Callbacks are also problematic, use Promises instead
Similar to the jQuery issues, is the issue of callbacks. If you are used to passing in callbacks to a function to handle asynchronous functions you might also have issues with TypeScript classes. For example:
would throw an error when the service was updated, since toastService doesn’t exist in the global scope. You have to remember to perform all your class callbacks with .call or .apply. So to properly use a callback in a TypeScript class you must use arrow functions and either call or apply:
However, a much better way to go is to use promises rather than callbacks, preferably the Q library or the $q service in Angular, or if worse comes to worse using jQuery.Deferred (though if you are using jQuery 3, its Deferred is Promises/A+ compliant).
The above code becomes much nicer (especially with Generics in TypeScript) using promises:
Tip 10 - Go with the spirit of TypeScript instead of trying to trick it
and we converted it TypeScript
you are going to get a warning that a number can’t be converted to an object. So either you for the type:
**Note: use [as any rather than (
But a better way is to go with the spirit of TypeScript and not mutate types, so instead of changing the type of arguments, create variables in the function and assign them the proper value.
The nice thing about using a parameters interface, is that the values are checked by the compiler, but the defination of the interface takes no space in the compiled code.
Tip 11 - Enjoy