Note: This post originally appeared on Codeburst.io
One of the biggest differences between Angular 1.5+ and 2 is the latter’s approach to dependency injection. Back on Angular 1.5 there were a couple of ways to configure dependency injection but they relied on naming things consistently. If you needed the “$http” service you had to specify that by specifying a configuration explicitly mentioning “$http”. And unfortunately, it was also possible to do this with implicit annotations which would cause minification to break your code.
Angular2 with TypeScript dramatically improves on this by introducing a type based DI system where you just correctly specify the type of an injectable you want and Angular will handle the rest. But since TypeScript compiles to JavaScript and in doing so wipes any type information how can this DI system work? It’s not magic so lets take a look!
TypeScript Decorators
The first piece of the puzzle is the @Injectable TypeScript decorator which marks a class as available for DI. TypeScript decorators are a language feature which enables developers to use annotations to modify the behavior of classes, methods, and properties at run time. Within Angular, the @Injectable annotation is “class annotation” which Angular uses to register information about the target class with the framework. The relevant framework code is in the packages/core/src/di namespace with the most interesting files being:
Reading through the code is a bit challenging but the overall idea is that the framework keeps track of classes that have been annotated with @Injectable and then has a “get” method to correctly construct instances of those classes. OK but what about that type information?
reflect-metadata
The second piece of the DI puzzle is the reflect-metadata package and TypeScript’s “emitDecoratorMetadata” option. When used together they will cause the TypeScript compiler to output metadata for classes that have been annotated with a class decorator. And most importantly this metadata is accessible at runtime by userland JavaScript.
Concretely, the Angular DI system uses this metadata to introspect the arguments that the constructor a class marked @Injectable requires. And then naturally using that information the DI framework can automatically construct a class on demand for you.
An example DI system
Finally what we’ve all been waiting for, some sample code! In order to compile, you’ll need to enable the experimentalDecorators and emitDecoratorMetadata compiler flags, install the reflect-metadata package, and also use a recent version of TypeScript.
If you compile and run it you should get the following output:
Car { ex: [ Engine { maker: ‘Tesla’, displacement: 500 } ] }
So as expected we retrieved a Car without manually constructing an Engine and the Engine constructor was correctly invoked since the class properties were set.
Couple of things of note:
- I created the “Newable” type alias to represent a constructor
- The Inject decorator calls the Injector class in order to create some encapsulation
- On line 10, the Reflect.getOwnMetadata(“design:paramtypes”, originalConstructor); call retrieves constructor information for the class that the decorator has been applied to.
- Line 18 uses bind() to modify the class constructor to use the Injector to retrieve instances of the required classes
And that’s about it. After creating this example it’s clear that TypeScript’s decorators and the reflect-metadata are both powerful tools and I’m excited to explore what else they could enable.
Interested in adopting Angular or TypeScript at your organization? We’d love to chat.