Tabris Decorators Part 1: Intro to TypeScript Decorators

This article will give you some practical know-how on TypeScript decorators, what they are, how to write your own, and how they can improve your code. It is part of a series of blog posts dedicated to a Tabris.js extension called tabris-decorators, which makes developing mobile apps with Tabris.js 2.x and TypeScript more convenient. The extension features data binding capabilities, enhanced event handling and dependency injection explicitly designed for the Tabris mobile framework.

TypeScript decorators are functions that can be used to manipulate a class definition at runtime, e.g. replacing parts of it or adding metadata. They can be called with the @ sign when placed before a member, a parameter, or the class itself:

Your own custom value-checking property decorator

Decorators are a great way to move very technical, repetitive code out of sight so you can concentrate on what the class is actually about. They are not TypeScript exclusive, but they are more powerful in TypeScript. To explain why let’s write a property decorator ourselves.

First, you must enable two compiler options in your tsconfig.json:

Okay, now assume you have a simple model class like this:

The benefit of writing and using such a class in TypeScript instead of JavaScript is obviously that you can (usually) rely on each of these properties to only hold a value of the appropriate type. That doesn’t mean that these are always useful values:

The usual way to prevent this would be to write property setter (and getter) to check that the number is positive. This turns one line into up to ten lines, depending on how you go about it.

Maybe it’s fine to fix this one instance of this issue, but to be consistent, you have that for every property in your project that requires some kind of validation that goes beyond what TypeScript can manage at compile time. Here is where a decorator can help:

Maybe this looks a bit like magic, but it isn’t. Actually, positive is just a normal function:

Okay, so what does this do? First, the parameters:  prototype is the object that the class instance methods, including setters and getters, are stored in. It’s NOT the instance itself, the instance inherits from this object.  property is the name of the property the decorators was applied to. The call to Object.defineProperty creates a setter and getter on the prototype, so that any access to person.age will go through them.

We set enumrableto true to keep the previous behavior where age is picked up by for ... in loops and JSON.stringify.

Let’s take a closer look at the setter:

The check should speak for itself. If the value is smaller than zero, throw an error, the property will keep its previous value.

While this only checks the value to be positive, in practice I would strongly recommend additional criteria like excluding NaN and infinity. And of course, you could modify the behavior to only printing a warning and not setting the value, or adjust the value to be at least zero, and so on.

The tricky part is where to actually store the value. You can’t use a closure or the prototype itself, that would make all instances of Person have the same age value. However, we do have access to the instance object, since that is the context (value of this) the setter is executed in. (That’s why it’s important NOT to use arrow functions to define the setter.)

You could just follow the usual pattern of storing the value in a pseudo-private instance property that has the same name as the property with a prefix, e.g., _age, BUT: This could lead to a number of unexpected behaviors since nowhere in the actual class definition do you see such a member declared.

The solution is another relatively new JavaScript type called symbols. Every call of Symbol() creates a completely unique value that may be used as an object property identifier that is not enumerable. In other words: Only those with access to this exact symbol can also access any properties set via the symbol. This finally solves the old JavaScript issue of having (runtime) private properties. So by creating a symbol via…

…and then using it in the setter…

…and the getter…

we have a way of attaching data to the instance of Person that no other code has access to.

Who do you trust? – A generic type check decorator

Even if the TypeScript type system is sufficient to express what values are valid for your property, there is no guarantee that it is respected at runtime. The obvious example would be any kind of interaction with code that was written in JavaScript, which has no compile-time checks whatsoever. But let’s stay in TypeScript-land. Wouldn’t it be cool to do something like this:

In this scenario, fetchData may read the values for 'jack' from a database somewhere, a user-provided file, or just something your own application put into localStorage. If you are confident that these entities are reliably providing valid data, you can just leave it at that. But the point of TypeScript is that you can be sure a boolean property always actually to be a boolean, and not maybe a string that looks like a boolean, e.g., 'false'.

Of course, you could write decorators as above for every property type you use – this is what you would have to do in pure JavaScript. But aside from the considerable amount of additional code – you would have to pay extra attention to always use the right decorator for each property, and not accidentally add something like @checkIsString to a boolean property. (Of course the same is true for the above @positive example, more on that later.)

Instead, imagine something like this:

One decorator, valid for any data type your JSON may contain. How can this be done? Like this:

It follows mostly the same pattern as the first example, but the type that is expected is dynamic. This is where the emitDecoratorMetadata compiler option comes in: It makes tsc add type metadata to all decorated properties and parameters, which can be read via the extended Reflect API. This requires an additional module that we install on your command line with npm i reflect-metadata and load in code via import 'reflect-metadata'.

You can now get the property type like this:

Now constr is the constructor function that would create a valid value, or in our case of primitives, their respective boxed type. For example, a number property is represented by the Number constructor. Usually you only use these kinds of constructors for their static methods, but in this case, we actually exploit the fact that their names match that of the results of a typeof check. Therefore, this snippet provides a reasonably secure type check:

The result:

Unfortunately, this generic approach does not work with advanced types such as unions. In these cases, the value of type is just Object. To prevent false positives you should throw an Error when this happens. To work around this you need to either write dedicated decorators or avoid advanced types. Also, for type-specific decorators such as @positive you may want to throw if type does not match what the decorator was designed to check for.

If you wanted to, you could also use the property metadata to check that all values have been set after initializing the instance. Another use case would be to check method/constructor parameters. That would be a bit more involved, but very useful if you are writing a TypeScript library that will be consumed in a JavaScript project. In that case, any public API couldn’t trust a method caller to respect the parameter types, so runtime checks are a must.

Decorators Factories

You can create even more powerful decorators by providing a factory that is then called inline. For example, instead of creating a @email decorator, write a factory that takes a regular expression:

And now you can do this…

… but of course you can also use the decorator for other properties that expect other kinds of strings.