News

Get robust JavaScript code with the TypeScript library

Structure your JS code more rigidly than ever before with the help of Microsoft’s TypeScript

typescriptMain

Structure your JS code more rigidly than ever before with the help of Microsoft’s TypeScript

typescript

JavaScript’s liberal syntax declaration lets developers do all kinds of weird and wonderful things. Programmers who grew up on more restrictive class-based systems tend to need some time to aquaint themselves to the new working environment.

Google engineer Addy Osmani collected design patterns which helps this process – when, and only when, applied correctly. Duck typing is even worse though: it permits the creation of bugs which occur only when the code path in question is executed. Murphy’s law then ensures that this will happen on the computer of an important client. TypeScript solves these problems by creating a statically typed dialect of JavaScript which is verified via a more stringent execution environment. Transgressions are punished mercilessly, defective code never makes it to the browser.

Microsoft does its magic via a process called transpilation. A special compiler transforms the TypeScript code to vanilla JavaScript, which can then be run in a JavaScript VM of choice. State-of-the-art JavaScript runtimes are so fast that they can be used as execution environment for arbitrary code – some developers even go as far as to transpile C++ to a JavaScript-based VM.


GET THE CODE FOR THIS TUTORIAL

1. Install Visual Studio

Even though TypeScript can also be run from the command line, we will use Microsoft’s free version of Visual Studio 2015. Download it by visiting visualstudio.com/en-us/products/visual-studio-community-vs.aspx and install it like any other application.

2. Create a project

Click New Project in order to open the new project wizard. Then proceed to opening the TypeScript tab, where a new solution based on the HTML application with TypeScript template is to be created. It will contain an HTML file and an accompanying TS file containing the actual TypeScript code.

3. Wire a button

The following steps will demonstrate various features of the runtime via an ever-expanding method. It must be triggered from a button – open the HTML file and append the markup shown in the source code accompanying this step. Then, replace the window.onload block in the TS file with the function specified.

<body>
<h1>TypeScript HTML App</h1>
<button onclick="btnClicked()">Button clicked</button>
</body>
//snip
function btnClicked()
{
alert("Hello TS");
}

4. A question of typing

TypeScript takes its name from its typing capabilities. The snippet accompanying this step shows a few variable declarations. Be aware that variables don’t necessarily need to have a type – if any is specified, the variable in question will not be subject to input validation.

function btnClicked()
{
var myString: String;
var myNumber: Number;
var myBool: Boolean;
var myAny: any;
}

5. Validation

TypeScript’s capabilities can be validated by assigning invalid elements to the variables created in the previous step. Visual Studio will flag them down the moment the file has been saved. It will, furthermore, prevent deployment until any and all objectionable passages have been remedied.

6. Create an enum

If variables are to contain but a few predefined values, using an enum is the way to go. The snippet accompanying this step demonstrates the definition of an enum handling aircraft types. After that, a new instance of the enum is created and a value is assigned in order to demonstrate its handling.

enum Aircraft {
MIG21,
MIG25,
MIG29,
MIG31
}
function btnClicked()
{
var plane: Aircraft;
plane = Aircraft.MIG25;
}

7. Create a class

Next up: we’ll work on real classes. Remove the declaration of the greeter class, and then replace it with the following bit of code. Our AircraftManager class contains a member variable, a member function and a constructor which is used to set its value during the initialisation of the object instance.

class AircraftManager {
myWhatAmI: Aircraft;
constructor(aWhat:Aircraft) {
this.myWhatAmI=aWhat;
}
sayHello() {
alert("Hello");
}
}

8. Spawn an instance

The process of pawning an instance is as easy as invoking the new operator on the class name, which can furthermore be used as a variable type. By and large, any objects that are spawned from TypeScript classes behave just like normal JavaScript objects – accessing their members and member functions via the . operator is a non-brainer.

function btnClicked()
{
var planeManager: AircraftManager;
planeManager = new AircraftManager(Aircraft.MIG21);
planeManager.sayHello();
}

9. Hide a member

TypeScript treats all members of a class as public by default. This behaviour can be modified by making use of the private keyword. This keyword can be appended to both member functions and variables – in both cases, external access is no longer permitted by the transpiler.

class AircraftManager {
private myWhatAmI: Aircraft;
constructor(aWhat:Aircraft) {
this.myWhatAmI=aWhat;
}
private sayHello() {
alert("Hello");
}
}

10. Yield results

TypeScript’s zealotic quality control algorithms, of course, can also make amends for all kinds of class-related oddness. We can try this out with two methods: by attempting to invoke the constructor with a wrongly typed parameter or via an access attempt addressed at a private variable.

11. Check values

Accessors are among some of the oldest structuring aides known to programmers. Our snippet that can be seen below demonstrates the adding of a property which checks the values passed into it before committing them to the data store found inside the class. The getter could, in theory, also be expanded to modify the values returned.

class AircraftManager {
private _WhatAmI: Aircraft;
get myWhatAmI(): string
{
return "An Aircraft";
}
set myWhatAmI(what: string)
{
if (what == "MIG21")
{
this._WhatAmI = Aircraft.MIG21;
}
}

12. Function cut short

Typing time is one of the most important ‘time wasters’ that can be encountered during software development. TypeScript permits you to set sensible defaults for parameters, which can then be invoked with a shorter parameter list. Please be aware though that all non-optional parameters will need to be in front of the optional ones.

class AircraftManager {
private _WhatAmI: Aircraft;
. . .
constructor(aWhat: Aircraft = Aircraft.MIG21) {
this._WhatAmI=aWhat;
}
. . .

13. Creation of parameters

Functions with a variable number of parameters can be extraordinarily helpful at solving some rarely encountered problems. TypeScript provides a facility which makes the creation of variable-parameter functions really easy – all you have to do is take a look at the code accompanying this step below.

{
return first + " " + rest.join(" ");
}

14. Shared static elements

Static elements are shared between all instances of a class: although this commonly maligned pattern could potentially lead to some brittle code if it is overused. However, static elements can also be really helpful when it is actually applied correctly. TypeScript delights its users with the presence of a static keyword, and this behaves as expected:

class AircraftManager
{
. . .
static sayHello() {
alert("Hello");
}
}
function btnClicked()
{
AircraftManager.sayHello();
}

15. Inheritance

Making classes inherit from one another simplifies the modelling of real-world relationships. Derived classes can, furthermore, overwrite the behaviour of their hosts – for example, the BetterManager replaces the message that is shown by the normal sayHello() function if invoked.

class AircraftManager {
. . .
sayHello() {
alert("Hello");
}
}
class BetterManager extends AircraftManager
{
sayHello()
{
super.sayHello();
alert("Greetings from the better Manager!");
}
}
function btnClicked()
{
var aManager: BetterManager;
aManager = new BetterManager();
aManager.sayHello();
}

16. Access mother object

TypeScript does not hide the mother object instance from view. It can, instead, be accessed via the super keyword. Our snippet below demonstrates the usage of super in a constructor and in a member function – it could not be easier.

class BetterManager extends AircraftManager
{
sayHello()
{
super.sayHello();
alert("Greetings from the better Manager!");
}
}

17. Enforce presence via interfaces

JavaScript’s duck typing is a never-ending source of pain and this is because everything is valid for everything, that is until a NullReferenceException occurs though. Using an interface enables developers to specify the presence of member variables and/or functions – and any elements that are not confirming are therefore not allowed to pass.

interface AnObject
{
myName: String;
myNumber: Number;
}
function worker(a: AnObject) { }
function btnClicked()
{
var anObject = { myName: "AnElement", myNumber: 22 };
worker(anObject);
}

18. Make parts optional

An old adage states that exceptions can prove the validity of rules. Interfaces can be configured to contain optional members and of course these don’t necessarily need to be implemented. This feature, unfortunately, is not particularly helpful for us and that’s because crashes can occur if the callee forgets to check the presence of the implementation.

interface AnObject
{
myName: String;
myNumber?: Number;
}
function worker(a: AnObject) { }
function btnClicked()
{
var anObject = { myName: "AnElement"};
worker(anObject);
}

19. Implement interfaces

Classes can be designated as implementations of particular interfaces. This is accomplished thanks to use of the implements keyword: if ‘implements’ is present and the class misses declarations that are required in the interface then what happens is that a compiler error will be raised in order to notify the developer about his unforgiveable omission.

interface AnObject {
myName: String;
myNumber?: Number;
}
class SomeClass implements AnObject
{
myName: String;
myNumber: Number;
}

20. Modularise

The TypeScript transpiler does not merge the individual parts of a library into one file – instead, each TS file gets transformed into an individual JS file. Do be aware though that each JS file must be included into the website separately in order to be able to use the module’s content.

21. Go generic

Developers can side-step the type-checking process and all they have to do to do this is make use of the Any keyword. The use of generics provide a safer way to creating type-agnostic classes and/or functions: the parameter informs the compiler about the type that is going to be used by a particular instance, and this can then be enforced with zealotism.

function genericF<T>(myVal: T)
{
return myVal;
}
function btnClicked()
{
genericF<Number>(22);
genericF<String>(22);
}

22. Mixin on the loose

Keep in mind that creating complex inheritance structures is not always the best solution. Mixins are ‘building block classes’ which provide a small set of functionality, and these mixins can then be integrated into larger classes. As an example, let us take a bit of code from the documentation which creates activity management logic.

class Activatable {
isActive: boolean;
activate() {
this.isActive = true;
}
deactivate() {
this.isActive = false;
}
}

23. Use applyMixing

Mixins are instantiated via the implements keyword. The host class must contain a stub implementation, and this is then overwritten at runtime by making use of the applyMixing method.

class SmartObject implements Disposable, Activatable {
. . .
// Activatable
isActive: boolean = false;
activate: () => void;
deactivate: () => void;
}
applyMixins(SmartObject, [Disposable, Activatable])
var smartObj = new SmartObject();
setTimeout(() => smartObj.interact(), 1000);

24. Mix it up

The actual deployment of the mixin must be done at runtime via the function shown in the code snippet shown below. Due to the complexity of the mixins, it’s important for you to consult documentation before proceeding further.

function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
derivedCtor.prototype[name] = baseCtor.prototype[name];
})
});
}
×