During the last weeks I've been working in a personal Node project using Typescript. As the number of components grew I started to struggle when any update in the application bootstrap was required. Since most of my refactors are of the extraction type, this happened quite often.
This is clearly the type of situations where a Dependency Injection Container comes in handy. I dug into existing solutions but none of them looked like what I had in mind, so I decided to write my own.
I have some experience writing this type of frameworks, and also wanted to explore the support for ES6 Annotations added in Typescript 1.5.
I'm quite happy with the result, the resulting code is clean and expressive, and will definitely look familiar to developers coming from other languages.
Injecting dependencies
Unsurprisingly, standard @Inject
annotations are used to define component dependencies:
class NotificationsStore {
@Inject('repository') // injection by id
documentRepository : DocumentRepository;
private notifications : Array<Notifications>;
@PostConstruct
init() {
// invoked after all injections have been resolved
console.log('NotificationsStore initialised');
this.documentRepository.fetch('notifications')
.then((data) => this.notifications = data);
}
@Destroy
destroy() {
console.log('NotificationsStore destroyed');
}
}
@PostConstruct
and @Destroy
are used to control the lifecycle of the component:
@PostConstruct
is invoked once all the dependencies have been set. When the initialisation of the component depends on its dependencies @PostConstruct
methods should be used instead of the constructor.
@Destroy
can be used to clean up the component before all the dependencies are nullified.
Creating the container
Creating a new container is straightforward using ContainerBuilder
class. Multiple containers can coexist in the same application.
let container = ContainerBuilder.create();
container.add(new NotificationsStore());
container.add(new DocumentRepository(), 'repository');
container.init(); // invokes all @PostConstruct methods
// retrieves an instance from the container
let repo = container.get('repository')
Annotation support and ECMAScript
At this moment the syntax of container-ts is limited by the support of ES6 and ES7 in typescript. In the previous example dependencies are resolved by id, but the container also supports a not too elegant resolution by type:
class NotificationsStore {
@Inject(() => DocumentRepository) // injection by type
documentRepository : DocumentRepository;
...
}
let repo = container.get(DocumentRepository)
Hopefully new meta and reflection additions to the language will open the door to nicer options inject dependencies.
Check the status of the project at github or test in your project with npm install container-ts --save