< back

Container-ts, Dependency Injection for Typescript using ES6 Annotations

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

comments powered by Disqus