EyeOhSee is an IOC framework. It uses TypeScript attributes and metadata to perform constructor injection.
- Constructor injection
- Registration of transient dependencies -
@InstancePerDependency()
- Registration of singleton dependencies -
@SingleInstance()
- Registration of implementations for abstract services -
@InstancePerDependency(BaseClass)
and@SingleInstance(BaseClass)
- Registration of a single implementation for multiple services -
@SingleInstance(BaseClassA, BaseClassB)
and@InstancePerDependency(BaseClassA, BaseClassB)
- Array injection -
@ArrayOf(BaseClass)
- Factory injection -
@Factory(ReturnType)
- Parameterized factory injection -
@Factory(ParamTypeA, ParamTypeB, ReturnType)
- Automatic disposal of resolved instances -
@Disposable()
and@Disposable(instance => instance.disposeMethod()
- Child-containers/unit-of-work injection -
@UnitOfWork(OwnedType)
- Parameterized child-container/unit-of-work factories -
@UnitOfWork(ParamTypeA, ParamTypeB, OwnedType)
- Automatic disposal of container descendants
- Ability to override attribute registration for testing using container API
- Registration of singleton-in-scope - @InstancePerScope("MyScopeName")
// This will only be constructed once
@SingleInstance()
class MySingleton { ... }
// This is what consumers resolve
class MyBaseClass { ... }
// This is the implementation
@SingleInstance(MyBaseClass)
class MySingleton extends MyBaseClass { ... }
// This is one of the interfaces that consumers resolve
class MyBaseClassA { ... }
// This is another of the interfaces that consumers resolve
class MyBaseClassB { ... }
// This is the implementation
@SingleInstance(MyBaseClassA, MyBaseClassB)
class MySingleton implements MyBaseClassA, MyBaseClassB { ... }
// This will be constructed for each dependency
@InstancePerDependency()
class MyTransient { ... }
// This is what consumers resolve
class MyBaseClass { ... }
// This is the implementation
@InstancePerDependency(MyBaseClass)
class MyTransient extends MyBaseClass { ... }
// This is one of the interfaces that consumers resolve
class MyBaseClassA { ... }
// This is another of the interfaces that consumers resolve
class MyBaseClassB { ... }
// This is the implementation
@InstancePerDependency(MyBaseClassA, MyBaseClassB)
class MyTransient implements MyBaseClassA, MyBaseClassB { ... }
// This is the service interface
class MyBaseClass { ... }
// This is one implementation
@InstancePerDependency(MyBaseClass)
class MyFirstImpl { ... }
// This is another implementation
// Notice that you can mix resolution strategies of implementations
@SingleInstance(MyBaseClass)
class MySecondImpl { ... }
// This is the consumer
@InstancePerDependency()
class MyConsumer {
constructor(@ArrayOf(MyBaseClass) myThings: MyBaseClass[]) { ... }
...
}
// The service
@InstancePerDependency()
class MyService { ... }
// The consumer
@InstancePerDependency()
class MyConsumer {
constructor(@Factory(MyService) private factory: () => MyService) { ... }
...
public later() {
const myServiceInstance = this.factory();
...
}
}
class ToeDirection {
public static BigToeOnRight = new ToeDirection();
public static BigToeOnLeft = new ToeDirection();
}
// A foot has a toe direction
@InstancePerDependency()
class Foot {
constructor(toeDirection: ToeDirection) { ... }
...
}
// A leg has a foot
@InstancePerDependency()
class Leg {
constructor(foot: Foot) { ... }
...
}
// A robot has two legs
@InstancePerDependency()
class Robot {
private leftLeg: Leg;
private rightLeg: Leg;
// Factory attributes can take multiple parameters but here we only use one
constructor(@Factory(ToeDirection, Leg) legFactory: (toeDirection: ToeDirection) => Leg) {
// We construct each leg specifying how ToeDirection should be resolved by descendants
this.leftLeg = legFactory(ToeDirection.BigToeOnRight);
this.rightLeg = legFactory(ToeDirection.BigToeOnLeft);
}
...
}
// It calls the disposal method when the container it was resolved from is disposed
// It defaults to calling a method called "dispose"
@InstancePerDependency()
@Disposable()
class MyFirstResource {
dispose() {
// Clean up stuff here
}
}
// We can override the method it calls when the container is disposed
@InstancePerDependency()
@Disposable(instance => instance.cleanUp())
class MyFirstResource {
cleanUp() {
// Clean up stuff here
}
}
// Service used inside unit of work
@InstancePerDependency()
@Disposable()
class MyService {
dispose() { ... }
}
// Business aspect with some lifetime that is shorter than that of the application
@InstancePerDependency()
class MyRequest {
constructor(service: MyService)
}
// The application that handles the lifetimes of those business aspects
@InstancePerDependency()
class MyApplication {
...
constructor(@UnitOfWork(MyRequest) private requestFactory: () => IUnitOfWork<MyRequest>) { ... }
...
onStartRequest(id) {
...
this.requests[id] = this.requestFactory();
...
}
onFinishRequest(id) {
...
// Disposes the resolved instance of MyService for the given request
this.requests[id].dispose();
...
}
}
// Service used inside unit of work
@InstancePerDependency()
@Disposable()
class MyService {
// Accepts some configuration
constructor(config: Config) { ... }
dispose() { ... }
}
// Business aspect with some lifetime that is shorter than that of the application
@InstancePerDependency()
class MyRequest {
constructor(service: MyService)
}
// The application that handles the lifetimes of those business aspects
@InstancePerDependency()
class MyApplication {
...
constructor(@UnitOfWork(Config, MyRequest) private requestFactory: (config: Config) => IUnitOfWork<MyRequest>) { ... }
...
onStartRequest(id) {
...
// unit of work created with parameter that will be resolved by MyService instance
this.requests[id] = this.requestFactory(this.config);
...
}
onFinishRequest(id) {
...
// Disposes the resolved instance of MyService for the given request
this.requests[id].dispose();
...
}
}