An in-depth understanding of the implementation of TypeScript’s modifiers, which make it possible for JavaScript to implement reflection and dependency injection.
The tutorial is divided into four main parts
- Part 1: Method modifiers
- Part I: Property modifiers & class modifiers
- Part III: Parameter Modifiers & Modifier Factories
- Part IV: Type serialization & metadata reflection API
In this article we will learn
- Why our JavaScript needs reflection
- The metadata reflection API
- Basic type serialization
- Serialization of complex types
Why is reflection needed in JavaScript?
Reflection is often used to describe code or review other code in the same system
Reflection is very useful in composition, dependency injection, runtime type assertion, testing
As our javascript applications get bigger and bigger, we start to need tools (like dependency inversion control, runtime type assertions) to manage the growing complexity of our applications. The problem now is that JavaScript does not have reflection and these tools or features will not be implemented, but some powerful programming languages implement reflection like C# or Java.
A powerful reflection API would allow us to test an unknown object at runtime and find all the information about it. We expect to find the following information
- The name of the entity
- The type of the entity
- That interface is implemented by the entity
- the name and type of the entity’s properties
- the name and type of the entity’s constructor parameters
In JavaScript we can use that function Object.getOwnPropertyDescriptor() or Object.keys() to find some information about the entity, but we need reflection to implement more powerful tools.
However, these situations will change, because TypeScript starts to support some reflection features, let’s take a look at them.
Metadata Reflection API
The TypeScript team developers used the Polyfilli shim to add the reflection API to ES7. The TypeScript compiler can now emit some serialized design-time metadata types for decorators.
We can use the metadata reflection API by using the reflect-metadata package.
|
|
We have to use TypeScript version 1.5 onwards and set the compiler logo emitDecoratorMetadata to true, we also need to include the reflect-metadata.d.ts file and load Reflect.js.
We next implement our own modifier and use the reflect metadata design key, but for now there are three types of design keys available
- type of metadatau using the metadata key “design:type”.
- type of parameters metadata use metadata key “design:paramtypes”
- return type metadata use metadata key “design:returntype”
Let’s see a few examples.
Get the type metadata of an attribute using the reflect metadata API
Let’s declare an attribute modifier.
We can apply it to an attribute of a class.
The above console will output the following.
|
|
Get metadata of parameter type using reflect metadata API
Let’s declare a parameter modifier.
We apply it to a method of a class and get information about the type of the parameters.
|
|
The console in the above example will output the following.
|
|
Get the metadata of the return type using the reflect metadata API
We can also get information about the return type of the method using the “design:returntype” metadata key
|
|
Serialization of base types
Let’s take a look at the design:paramtypes example above again. Notice that the interface IFoo and the literal object { test : string}
have been serialized as objects, this is because TypeScript only supports serialization of base types, here are the rules for serialization of base types.
- Numeric serialized as numeric
string
serialized asString
boolean
serialized asBoolean
any
serialized asObject
void
serializes asundefined
Array
serialized asArray
- if
Tuple
serialized asArray
- If an
Class
serializes as a constructor for a class - If an
Enum
serialized it asNumber
- If an
Enum
serialized
asNumber
- If it has at least one call signature, then serialize it as
Function
- otherwise serialized as object
Object
, including interfaces
Interfaces and literal objects may also use complex type serialization in the future, but it is not available now.
Serialization of complex types
The TypeScript team is working on a proposal that will allow us to generate metadata for complex types.
Their proposal describes how some complex types will be serialized. The serialization rules above will still be used for basic types, but complex types will use a different serialization logic. In the proposal, there is a basic type used to describe all possible types.
We can also find classes that are used to describe each of the possible types. For example, we can find the class foo <bar> {/ * ... * /}
.
|
|
As we saw above, there will be an attribute indicating the implemented interface.
This information can be used to perform certain operations, such as verifying that entities implement certain interfaces at runtime, which could be really useful for IoC containers.
We don’t know when complex type serialization support will be added to TypeScript, but we can’t wait, as we plan to use it to add some cool features to our excellent IoC container for JavaScript, InversifyJS.