IoC Container for Typescript
This is a lightweight annotation-based dependency injection container for typescript.
It can be used on browser or on node.js server code.
Table of Contents
- IoC Container for Typescript
This library only works with typescript. Ensure it is installed:
npm install typescript -g
To install typescript-ioc:
npm install typescript-ioc
Typescript-ioc requires the following TypeScript compilation options in your tsconfig.json file:
That's it. You can just call now:
And the dependencies will be resolved.
You can also inject constructor parameters, like:
and then, if you make an injection to this class, like...
The container will create an instance of PersonService that receives the PersonDAO from the container on its constructor. But you can still call:
And pass your own instance of PersonDAO to PersonService.
Note that any type with a constructor can be injected.
You don't have to do anything special to work with sub-types.
The above example will work as expected.
You can use scopes to manage your instances as:
So, we can create a lot of PersonController instances, but all of them will share the same singleton instance of PersonService
We have two pre defined scopes (Scope.Singleton and Scope.Local), but you can define your own custom Scope. You just have to extend the Scope abstract class:
Providers can be used as a factory for instances created by the IoC Container.
Providing implementation for base classes
It is possible to tell the container to use one class as the implementation for a super class.
So, everywhere you inject a PersonDAO will receive a ProgrammerDAO instance instead. However, is still possible to create PersonDAO instances through its constructor, like:
// a personDAO instance will be returned,// with its dependecies resolved by container;
The @AutoWired annotation
The @AutoWired annotation transforms the annotated class, changing its constructor. So, any auto wired class will have its instantiation delegated to the IoC Container even when its constructor is called directly.
It is usefull, for example, to avoid direct instantiation of Singletons.
If anybody try to invoke:
That instantiation will be delegated to the container. In the case of a Singleton class, the container will not allow more than one instantiation and it could cause a TypeError.
The Container class
You can also bind types directly to Container resolution.
// it will override any annotation configurationContainer.bindPersonDAO.toProgrammerDAO.scopeScope.Local;// that will make any injection to Date to return// the same instance, created when the first call is executed.Container.bindDate.toDate.scopeScope.Singleton;// it will ask the IoC Container to retrieve the instance.;
You can use the Ioc Container with AutoWired classes and with non AutoWired classes.
Container.bindPersonDAO;;// or;// personDAO.personRestProxy is defined. It was resolved by Container.
Singleton scopes also received a special handling.
; // throws a TypeError. Autowired Singleton classes can not be instantiated;Container.bindPersonDAO.providerpersonProvider; //Works OKContainer.bindPersonDAO.scopeScope.Local; // Now you are able to instantiate again; // Works again.
You can use snapshot and restore for testing or where you need to temporarily override a binding.
describe'Test Service with Mocks',;
Registering from multiple files
Typescript-ioc does not scan any folder looking for classes to be registered into the Container. Your classes must be previously imported.
So, when you import a file, the decorators around the classes are activated and your decorated classes are registered into the IoC Container. However, if you have some types that are not explicitly imported by your code, you need to tell the IoC Container that they must be included.
For example, suppose:
If PersonDAOImpl is saved in a file that is not explicitly imported by your code, you will need to manually add it to the Container.
You can do this through
Container.bind(), as previously showed, or you can use the
ContainerConfig class to configure the sources to be included:
;ContainerConfig.addSource'lib/*'; // You can use glob patterns here// orContainerConfig.addSource'controllers/*', 'baseFolder';// orContainerConfig.addSource, 'baseFolder';
You need to configure those sources only once, but before you try to use the objects that depends on these files. This configuration only makes sense in NodeJS code. In browser, all your script will be already packaged and included into the page and you will never need to worry about it. Browserify or webpack will do the job for you.
A note about classes and interfaces
Typescript interfaces only exist at development time, to ensure type checking. When compiled, they do not generate runtime code. This ensures good performance, but also means that is not possible to use interfaces as the type of a property being injected. There is no runtime information that could allow any reflection on interface type. Take a look at https://github.com/Microsoft/TypeScript/issues/3628 for more information about this.
So, this is not supported:
// NOT SUPPORTED
However there is no reason for panic. Typescript classes are much more than classes. It could have the same behavior that interfaces on other languages.
So it is possible to define an abstract class and then implement it as we do with interfaces:
// It works
The abstract class in this example has exactly the same semantic that the typescript interface on the previous example. The only difference is that it generates type information into the runtime code, making possible to implement some reflection on it.
To configure a node.js project to work with es6, you need to configure your tsconfig.json as:
And need to inform which version of the library you want to import. In Node JS environment, you can create a file called
ioc.config in the project root dir with the content:
In the place of use this
ioc.config file, you can change your import declaration to import the module 'typescript/es6'.
It was tested with browserify and webpack, but it should work with any other similar tool.
If you are using
compilerOtions.target = es5 in your tsconfig.json file, ensure you import the library as:
If you are using
compilerOtions.target = es6 or any newer version import as:
When you import the default library version
It is prefereable to configure your Singleton classes using @AutoWired. It is safer because it ensures that all configurations will be applied even if its constructor is called directly by the code.
Configure default implementations for classes using the @Provides annotation. If you need to change the implementation for some class, you just configure it direct into IoC Container.
// And later, if you need...Container.bindPersonDAO.toManagerDAO; //It will override any annotation
Another good practice is to group all your container configurations. It is easier to manage.
// and call..MyIoCConfigurations.configure;
- Circular injections are not supported
- You can only inject types that are already defined into your file. It can be solved by a @LazyInject on future releases