Security News
Fluent Assertions Faces Backlash After Abandoning Open Source Licensing
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
@baileyherbert/container
Advanced tools
This package contains a simple, lightweight dependency injection container. It's heavily inspired by tsyringe but has some cool additional features and uses my reflection library.
npm install @baileyherbert/container
Make sure your tsconfig.json
file specifies the following options:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
}
}
Import the global container from anywhere:
import { container } from '@baileyherbert/common';
Then register your types using injection tokens using the register methods.
container.register(ClassType);
container.register(ClassType, { useClass: ClassType });
container.register(ClassType, { useValue: new ClassType() });
container.register(ClassType, { useFactory: () => new ClassType() });
container.registerSingleton(ClassType);
container.registerSingleton(ClassType, ClassType);
container.registerInstance(new ClassType());
container.registerInstance(ClassType, new ClassType());
When registering a class or token provider, or a type, you can provide a lifecycle:
container.register(ClassType, { lifecycle: Lifecycle.Singleton });
container.register(ClassType, { useClass: ClassType }, { lifecycle: Lifecycle.ContainerScoped });
Transient
creates a new instance for each resolution. This is the default.Singleton
creates a single instance and caches it for subsequent resolutions.ContainerScoped
creates a single instance per container (i.e. child containers will get their own).For the container to successfully resolve dependencies, all classes added to it must have the @Injectable
decorator
applied.
@Injectable()
export class ClassType {}
You can also register a class as a singleton on the global container using the @Singleton
decorator. This will
also mark the class as injectable so there's no need to add the @Injectable
decorator.
@Singleton()
export class ClassType {}
You can also enable dependency injection on a class method by applying the @Injectable
decorator to it.
@Singleton()
export class ClassType {
@Injectable()
public methodWithDI() {
}
}
There is a @Transient
decorator that works just like @Singleton
but registers the class as transient instead.
@Transient()
export class ClassType {
}
Finally the @Token
decorator can be applied to method parameters in order to specify or override the injection
token. This is especially useful when dealing with circular dependencies.
public method(@Token('tokenName') param: any) {}
public method(@Token(Class) param: any) {}
public method(@Token(() => Class) param: any) {}
To resolve a single instance, use the resolve
method. The last provider to be registered will be used.
const instance = container.resolve(ClassType);
If multiple providers are registered, you can retrieve all of their instances as an array with the resolveAll
method.
const instances = container.resolveAll(ClassType);
You can create child containers on demand. By registering a dependency on a child container, you can override the
return value of the resolve
method. The resolveAll
method will return an array of dependencies from both containers
in the order of registration, and with the child container's dependencies last.
const child = container.createChildContainer();
child.registerInstance(ClassType, new ClassType());
To invoke methods with dependency injection, first create a dispatcher.
const dispatcher = container.createDispatcher();
You can add custom typed instances which override the container. You can also add named values. If the method has a parameter which fails to resolve with the container or has a primitive type, but has a matching named value, then the named value will be used.
dispatcher.setNamedParameter('name', 'John Doe');
dispatcher.setTypedParameter(ClassType, new ClassType());
Finally, use the invoke
method to resolve dependencies, execute, and get the return value.
const returnValue = dispatcher.invoke(object, 'methodName');
resolver
This helper manages global container instances and makes it easy for various parts of the application to retrieve a reference to specific containers.
If the global container is not sufficient, you can use named containers. Simply request a named container and it will be created and cached globally.
import { resolver } from '@baileyherbert/common';
const container = resolver.getInstance('name');
If your application is using multiple containers, you might be interested in storing a reference to the container used to construct an object. Generally, this would require injecting the container as a parameter.
The resolver instead makes the container available with the getConstructorInstance()
method, but note that this
method will throw an error if not called from within a constructor that has been invoked by the container during DI.
Here's a reliable pattern for storing the container that works even if the class is extended:
import { resolver } from '@baileyherbert/common';
export class DependencyInjectedClass {
protected container = resolver.getConstructorInstance();
public constructor() {
// Now all methods, including the constructor, has a reference to the container
this.container.resolve();
}
}
With a reference to the container, you could make it easier for nested components in your application to retrieve
top level objects, like a root App
object.
export class DependencyInjectedClass {
protected container = resolver.getConstructorInstance();
protected app = this.container.resolve(App);
}
When registering multiple values under a single token, context allows you to pick a specific instance out of the registry.
Context is useful because it allows you to do things like this:
class Service {
constructor(
@Context('db:one') db1: Database,
@Context('db:two') db2: Database
) {}
}
In the above example, we have two different Database
instances which have been registered with their database names
as context, allowing us to easily pick out the instance(s) we need while still using the Database
token.
You can use string tokens for the same effect, but this permits some organization and works nicely with the
resolveAll()
method.
When registering an instance or value, you can pass a context value of any type. If there is already a contextual value under the same token with the same name, it will be overwritten.
Please note that when supplying an array for your context, it will be destructured into multiple contexts. This only happens for arrays, not any other types (such as sets).
container.registerInstance(new Class(), 'context');
container.register(Class, {
useValue: new Class(),
useContext: 'context'
});
You can resolve a value from its context by passing the context into the resolve()
method.
container.resolve(Class, 'context');
You can also use the @Context()
decorator in a class or method parameter.
constructor(@Context('context') instance: Class) {}
You can also pass an array of contexts to match against. The container will attempt to resolve a value for each context in the order specified, until a match is found and returned.
container.resolve(Class, ['contextA', 'contextB']);
FAQs
Lightweight dependency injection for TypeScript.
The npm package @baileyherbert/container receives a total of 6 weekly downloads. As such, @baileyherbert/container popularity was classified as not popular.
We found that @baileyherbert/container demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
Research
Security News
Socket researchers uncover the risks of a malicious Python package targeting Discord developers.
Security News
The UK is proposing a bold ban on ransomware payments by public entities to disrupt cybercrime, protect critical services, and lead global cybersecurity efforts.