Syringe is a dependency injection library for TypeScript, with a binding syntax inspired by Angular 2's DI system. Unlike other DI libraries, it has a lot of built-in type-safety. It's also fundamentally asynchronous, making handling asynchronous dependencies painless.
|Type safety and static analysis via typed tokens||✓|
|Can express dependencies of any type, including TS interfaces||✓|
|Declarative binding syntax||✓|
Table of Contents
npm install -g tsd (if you don't already have
npm install syringe.ts --save-dev
tsd install es6-promise --save
Syringe is packaged as a UMD module, so it can be loaded via CommonJS, AMD, or even via a global (
To begin using Syringe, you need to create an
Injector has bindings, which bind
Tokens to a 'recipe' describing how the injector should construct that dependency (e.g. via new'ing up a class, a factory, etc.).
;// In a real-world app, you'd export your tokens from many files and import them here to create the Injector;injector.getTwoToken.then;
To compile and run:
tsc test.ts typings/tsd.d.ts --module commonjs
node test.js => prints 2
Type-safe? How so?
In the example above, TypeScript knows that
injector.get(TwoToken) returns a
Promise<number>, because the type of the dependency represented by
TwoToken is known to be
Similarly, if you try to take a string and bind it to
OneToken, TypeScript will error out. This makes Syringe far more powerful than other TS/JS DI libraries, where calling
any, forcing you to cast an assume that the types will be correct at runtime. It also means that when binding classes or factories, if the parameters to the class constructor or factory change from that of the binding tokens in type or arity, or vice-versa, TypeScript gives an error.
The same system of type-parametized tokens enables Syringe to correctly handle and type non-class dependencies e.g. interfaces where other frameworks cannot due to their dependency on TypeScript decorator metadata (which don't work for interfaces in particular) and/or class tokens (which, again, don't work with interfaces nor non-class types).
The most basic type of binding simply binds a token to a value that has no dependencies.
This binds a token to the return value of a factory function, which can iteslf specify dependencies. The tokens for the dependencies follow the factory function.
Thanks to Syringe being asynchronous from the ground up, handling asynchronous dependencies is seamless.
toAsyncFactory is similar to
toFactory but takes a factory returning a
Promise. This means that if you have a
string dependency whose value is dependent on an async file read or XHR request, that can be done in the async factory and Syringe will simply wait for the promise to resolve prior to constructing dependent values.
This is an extremely powerful feature. It means that your code will no longer have to handle Promises as far as dependencies are concerned, because Syringe will wait for them to resolve before constructing objects dependent on them. This also means that when testing your code you no longer have to pass in promises, resolve them, reject them, etc, which can get a bit dirty. You just pass in a synchronous value.
toClass creates a binding between a token and a class, meaning that the dependency will be created by constructing a new instance of that class. The class's dependencies will be passed into its constructor.
You can also specify a class's dependency tokens by decorating it with
Inject. Note that this requires that the decorators flag (
--experimentalDecorators) be passed to the TypeScript compiler and that you target at least ES5 (
Note that if Syringe has a class dependency that is both decorated with
Inject and has inline dependency tokens in the
toClass call, the latter will take precendence.
You can create
Injector hierarchies by passing in a parent
Injector when creating a new
This works as you'd expect in that if you
get a token from a child injector and it's not bound on the child, the parent is then checked. Here, when we get
twoToken's value, the dependency on
twoToken's factory will be retrieved from the parent injector because it is not bound on the child.
Sometimes it is desirable to construct a dependency in a context after that context has been created by the injector. For this, Syringe has a lazy feature, meaning that the dependency will not be retrieved until
.get is called on the lazy instance.
This can sometimes be useful to avoid cyclic dependency errors, if you know that a dependency is not needed until sometime after the injector has created other dependencies:
;;// Throws CyclicDependencyError, because A depends on B and B depends on A.injector.getAToken.then;
but, if we know that we don't need a reference to B prior to calling
A#foo, we can specify that we want
bToken to be injected lazily.
Note that this means that the client code has to handle the asynchrony usually handled by the injector behind the scenes.
The MIT License (MIT) Copyright (c) 2015 Matthew Hill Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.