Typesafety with TypeScript and Live-Coding

Rainer Hahnekamp speaking at viennaJS in June, 2017
Great talks, fired to your inbox 👌
No junk, no spam, just great talks. Unsubscribe any time.

About this talk

In this talk: static types in a JavaScript app using TypeScript and companion tools. Live coding done with Angular 2 / Spring.


- So hello. Hello, my name is Rainer Hahnekamp. I'm a software engineer. I'm also a freelancer and I'm currently with a large chocolate company and also an internet company in the education industry. Today I want to talk about the type-safety in JavaScript applications. That basically means that I will be talking about TypeScript, so if I might ask who is familiar with TypeScript, please raise your hands. Okay. I guess that's the majority. So the first part will be more or less a very short introduction to TypeScript. I want to show what it is all about, what it does and why you should use it. And in the second part I will talk about things that you should be aware of, tasks you have to do additionally if you want that your application is full type-safe. Because just switching the programming language will not be enough. So we start with the introduction. I have prepared a very short example. So the use case here is that we have a user that has bought shares. A share has a certain price and what we're doing here is we're calculating the total value of his portfolio, and we also give the user the possibility to add shares. As you can see, I have a very very simple html page. And the JavaScript file. And I'm defining two variables, the one is the amount and the second is the price of the share. Then I'm having a render function where I'm calculating the total value, I'm displaying it. And then I have here the event listener if the user wants to add shares. Please do not judge the quality of this html file as you can see I've added the script tag at the bottom. Of course I should wait for the event DOM ready to be fired and so on, but as it is just an introduction to show TypeScript I wanted to keep it as simple as possible. So this is how it looks like. As you can see it shows me the current amount. And if I'm saying no, I want to add one share to my portfolio, then something goes wrong, I have now 110 shares and the price is also not very good. The bug should be more or less obvious. I'm using here the input addition variable and I'm accessing the value property and since it is a string, I have a string concatenation so it turns out that the amount gets passed into a string so I have one and string 10 is this 110 and if we calculating it the total amount above then JavaScript handles it again as an integer since I'm doing here some multiplication. This is exactly the kind of error that happens when you are not using static type languages because static type languages usually come with a compiler and the compiler can check your variables against the types and can tell you if you're using types correctly, if the functions you are using are actually correct named and so on. As TypeScript is a superset of JavaScript, which means that each JavaScript code is already TypeScript code, it's actually very easy for us to immigrate to TypeScript, I simply have to rename my test.js to test.ts and execute the compiler on it. Doing that just now, you can see here. So I'm refactoring it more or less. And then I execute the TypeScript compiler. I'm using the pretty flag and we expect an error. And it says property value does not exist on type HTMLElement, so usually we would have expected that it tells us that we have a string concatenation, but it turns out that we have another error. Because the document get element by ID returns us and HTML element and it is not guaranteed or defined that each HTML element actually has a value property. So, does anybody know a documented on property that guarantees me that I'm getting an input element? Mine either, that's good, 'cause I don't want to say something wrong. So what I can do here is something called type assertion where I actually tell the compiler that in this case I'm smarter than him and that I override the type of the document get element by ID class. So it's here, so I'm doing this like that. Now I'm telling it, it's actually, oh. I'm telling that it actually is an HTML input element and I can execute the compiler again. And now finally it tells me the real bug, namely that I am assigning here a string to a variable that actually should be a number. How does TypeScript know this? Because obviously I haven't done anything except the type assertion. Well, this is something called a type inference. If you are assigning directly a value to a variable, like here I'm assigning 10, the TypeScript knows that this variable is actually a number. So I'm fixing this bug by saying parseInt and executing the compiler again. Now it's better, I'll execute the programme. Add one share, and it's the right answer. So usually what you really want to do in TypeScript is that you want to be sure that every function return type and also your variables are typed so that the TypeScript compiler doesn't need to guess and can end up in an any type, which means he simply doesn't know the value, the type. So just to show it, it goes like that. I'm adding now the types. So what I'm telling that I'm not interested and yeah, and total also is a number. And nothing changes. What might be interesting here is the generated JavaScript files, so as you can see it didn't really change that much. TypeScript just removed my non-JavaScript syntax, namely number and type assertion. So far, so good for the, yes? - [Student] Just one question. The HTML input element, is this kind of a casting or is this, - No. - [Student] Is this a type of? - That's called type assertion. I mean, actually it looks like casting or feels like casting. I have looked it up and the official documentation says it's not a casting since it doesn't anything during the runtime. So that's why they call it type assertion. What we are doing here, we do something like casting. But as you know that we are compiling through JavaScript, during the runtime nothing is done concerning the types because JavaScript simply can't do anything about it. So again, this was it for the introduction. Now to the second part. So given we have an application, we have written it in TypeScript and as good developers we know that we should not implement everything on our own. We should be looking out for libraries that can serve our use case and therefore our application usually depends on quite some external libraries like Moment shares, Lodash or even larger frameworks like Angular, React, whatever. The problem here is that these libraries are distributed by MPM as you all know, and they're usually distributed with minimised JavaScript. So again the TypeScript compiler doesn't really know anything if he just sees JavaScript. So he can't actually verify if you are using all these libraries in the correct way. So this is something that has to be solved. And then there's of course also on the other side, it's the same problem, usually when we have a web application we are communicating with a server backend, endpoint, AP, whatever you want to call it. And of course, the TypeScript compiler can't really tell us that our request types and response types are in the correct way like the endpoint actually requires it. And finally, if we are developing a library on our own, of course we also want that our consumers or users of our library, that they also can have the types of our code, so this is something that also has to be solved. The last one is actually the easiest since if you have an existing TypeScript source code and the TypeScript compiler or another tool in the ecosystem of TypeScript can help you by creating the so-called declaration files. This is the standard solution to this problem. You can compare declaration files to something to the Sierra files or Java interfaces more or less, so if I have a module then I can really define my module contains these classes or these objects or these functions. And my functions have these input types and also this output type. What's also worth mentioning is that only the declaration file wouldn't help you since you'll also require the extra library of course. Because the declaration file is only used by the TypeScript compiler and it never gets into the compiled JavaScript source. If it comes to external libraries we are lucky since most of the popular libraries, there exist already declaration files for them. If you are searching the internet for solutions for this problem then you might stumble upon these three or four terms like TSD, Typings, @types. And the question is here of course, which one should I use? Actually it turns out that the first two ones are generations and @types is the successor. So these days you should just use @types. A little bit of background story, in the early beginnings of TypeScript there was a private person who started to write declaration files on his own and he put it on a GitHub project called DefinitelyTyped. There were some qualities issues. As far as I know, the main problem was that only TSD executable which actually collected the declaration file from the DefinitelyTyped Project could only access the DefinitelyTyped and nothing like MPM or directly from GitHub from another repository. So the second generation was Typings. That tried to solve said problem, there were also some quality issues and finally Microsoft as the owner of TypeScript took over by creating @types and they said that they will now really take care of that everything is working, that the quality is a given status, an acceptable status. And what's here, the real difference is that you don't require any executable that actually collects through the definition files because types is like any other MPM module. So you just to put the types into your package.json and it gets installed into your modules and everything is fine. Coming to the backend. As I said, this is perhaps the most interesting part, it's also very challenging because there exists such a variety of technology platforms that can actually build your backend and so there is no general solution for this problem and I like just to show a pattern or an approach how you can tackle this problem by using code generators. What I would like to say is that is if you care about the type-safe implication then I don't think I have to mention that on the backend code there must be some kind of static type language. If you are already having a no JS backend then you can just switch to TypeScript and you have already your typings. Otherwise, if you're using a Java or C sharp from dot net, that kind of languages. Yeah, just, as I said I want to show now how you can solve this by using code generators. Of course, the worst solution is that you write your declaration files on your own because that ends like documenting code, in the beginning you have your code and you have a very well written documentation that matches more or less the code, but that during the life cycle of an application it forks somehow and if you have typings that are not up to the endpoint specifications then your developers will probably remove them and work in an untyped way. I have now the same application I had before, but now with a more real world scenario so I'm using Angular 2 as a web client and Spring on the server side. Spring is one of the most popular Java Frameworks. So it's in this also, static typed. I'm just starting now the server on Spring. And I have used Angular CLI which is a, which you should use if you start with a web application in Angular because it provides you with all the necessary code and build tools. So here I'm using webpack. Or Angular CLI is actually using webpack and not I. So the server is already running. Build process now starts up. Who is familiar with Angular 2 by the way? Okay. Finally we finished. Now I'm opening my application. As you can see, it looks more or less the same. I'm adding one and it works. So now for the code. As you can see, I have here import statements. With import statements I am already including these declaration files. With the case of Angular I am also very lucky since Angular is developed in TypeScript so they are already including the declaration files. Then I have this add component, this is something we call decorators and it is more of less you can see there's some kind of metadata that tells Angular how it should use this class in its context. Then we have our variables from before. We have the amount and we have the price. And the difference here is that we are now doing the calculation of the total value from the backend. So we have here an http service and as you can see they request type is quite complex large, so there is quite some potential for failures. Just showing. And as you can see it is not type and that's the main problem because if I'm having here a typo, like portfoil and it should compile. Just see if it, And you actually get an error without any compilation warning whatsoever and this is probably not what you want. And as I said before, one can use a code generators here so it turns out that there already exists a code generator for immigrated Java classes to TypeScript classes. And I have added one in my, I mean I don't want to talk too much about Java since it's another thing. As you can see, this is my build configuration for the Java and I'm here setting the classes I want to have recreated to TypeScript and now I'm saying please create with the declaration file into this directory and I can open it and as you can see, this is how a declaration file looks like, but now with the request and response Java classes in TypeScript. So what I can do now, I can use this declaration file in my application. That we set here. So this is finally the function that calls at the endpoint and I'm saying I have an add shares request. That's the type and I'm also it returns an add shares response, so as you already see, my ID understands TypeScript and also the TypeScript compiler at the bottom tells me that there is obviously something wrong, I'm fixing this, changing it to portfolio. And it is working fine again. So the same error can occur on the other side. For example our server side developer has the glorious idea to rename one of the variables of the response like he's saying okay, I want this to be named the total value. This is actually the Java class. I also has to fix it here. Then I would have the same problem, but as I said, I already integrated my TypeScript code generator into the build tool, so the endpoint is now automatically refreshed. Or at least it should be. Because if I now doing the same again, it just returns me an answer, but this time it returns me total value and this is something invariably I don't understand. So this is the kind with the live coding things that can go wrong. I'm now just triggering the, Pardon? Ah! Oh, coopa, good. Okay then what's left is that I have to restart my build tool so that it also gets the new information from the new declaration file. And in some moments it will tell us that us that also the total property here is wrong. Yep, property total does not exist. So we are changing it back to total value, as you can see also my ID supports now auto completion since it actually also can read the types. This is of course also an advantage if you want refactor application later on. 'Cause then it can also help you. And then the final test, and it's working. So here again, the overview. As I have shown, not shown, I just did it, you should use for the external rivalries the @types packages, for external systems you should look out for code generators if you are not using static type language then like PHP or Python, then you probably are using a framework and I am guessing that there is a high chance that your framework has some information about your endpoints and about the request and the responses and I would try to tackle it that way, that I get the information from the framework directly. And for the consumers you can use TTSgen, generates your declaration files. And my final advice, if you don't want to use TypeScript for whatever reason, you should add the @types for all your external libraries where they are available. You should look out for a solution similar which I've shown here with the code generators and the hardest part probably will be to add the TypeScript compiler to your build environment just to change your JavaScript files by using ks and you actually get verification if you're using your libraries in the correct way. And this is virtually for free. Thank you.