Basic usage
#
1. ProviderProvider describe how to resolve a dependency. Currently, DI has 5 types of provider:
#
1.1. useValueProvides a fixed value for the dependency throughout the application's lifetime.
import { Container } from '@cellularjs/di';
(async () => { const container = new Container();
await container.addProvider({ token: 'key', useValue: 'foo', });
await container.resolve('key'); // 'foo'})();
#
1.2. useFuncuseFunc
allows you to register a function as a dependency provider. This function is invoked whenever the dependency is requested.
(async () => { await container.addProvider({ token: 'key', useFunc: text => text, deps: ['foo'], });
await container.resolve('key'); // 'foo'})();
You should known
If you use class in deps
, it will be treated as a token, so you need to declare its provider.
class Foo { }
(async () => { await container.addProviders([ { token: Foo, useValue: 'foo' }, { token: 'key', useFunc: text => text, deps: [Foo], }, ]);
await container.resolve('key'); // 'foo'})();
#
1.3. useClassCreates a new instance of the dependency class each time it is requested. You can prevent creating new instance everytime by using "permanent" cycle.
class Foo { }
(async () => { await container.addProvider({ token: 'key', useClass: Foo, // cycle: "permanent" });
await container.resolve('key'); // Foo object})();
Using a class as useClass
provider:
class Bar { }
(async () => { await container.addProvider(Bar);
await container.resolve(Bar); // Bar object})();
#
1.4. useModuleWith this provider, you can use provider from other module without importing that module into current container.
import { Module, Container } from '@cellularjs/di';
class FooService { }class BarService { }
@Module({ providers: [FooService, BarService],})class FooModule { }
const container = new Container();
(async () => { await container.addProvider({ token: FooService, useModule: FooModule, });
await container.resolve<FooService>(FooService); // FooService object})();
#
1.5. useExistinguseExisting
allow you to make an alias for another provider, let see how it work.
import { Container } from '@cellularjs/di';
class Oak { }
class BigOak { }
const container = new Container();
(async () => { await container.addProviders([ { token: Oak, useValue: 'tree' }, { token: BigOak, useExisting: Oak }, ]);
await container.resolve(BigOak); // 'tree'})();
#
2. Inject dependencynote
For now, @cellularjs/di
only allow to inject dependency via constructor.
#
2.1. InjectableInjectable
is used to mark a class as injectable - container can inject dependencies into this class.
Technically, you need to use a decorator to decorate a class that you want to inject dependencies. Injectable
is a good choice if you don't know what decorator should be used.
import { Injectable, Container } from '@cellularjs/di';
class Foo { }class Bar { }
@Injectable()class FooBar { constructor( private foo: Foo, private bar: Bar, ) { }
run() { return (this.foo as string) + (this.bar as string); }}
const container = new Container();
(async () => { await container.addProviders([ { token: Foo, useValue: 'foo' }, { token: Bar, useValue: 'bar' }, { token: FooBar, useClass: FooBar }, ]);
const fooBar = await container.resolve<FooBar>(FooBar); fooBar.run(); // 'foobar'})();
#
2.2. InjectWhen you want to use a string, number, ... as token instead of a class, you will need Inject
decorator to inject dependency.
import { Inject, Container } from '@cellularjs/di';
// In this example, Foo class use a decorator - Inject,// so there is no need to use Injectable here.class Foo { constructor( @Inject('foo') public value: string, ) { }}
const container = new Container();(async () => { await container.addProviders([ { token: 'foo', useValue: 'this is foo' }, { token: Foo, useClass: Foo }, ]);
const foo = await container.resolve<Foo>(Foo); foo.value; // 'this is foo'})();
#
2.3. forwardRefExample: circular dependency issue.
// foo.tsimport { Injectable } from '@cellularjs/di';import { Bar } from './'
@Injectable()export class FooWithoutForwardRef { constructor( public bar: Bar, ) { }}
// bar.tsimport './foo';
export class Bar { }
// index.tsexport * from 'foo';export * from 'bar';
// somewhere.tsimport { Container } from '@cellularjs/di';import { FooWithoutForwardRef, Bar } from 'index';
const container = new Container();
(async () => { await container.addProviders([ { token: Bar, useClass: Bar }, { token: FooWithoutForwardRef, useClass: FooWithoutForwardRef }, ]);
await container.resolve(FooWithoutForwardRef); // error})();
Example: fix circular dependency issue with forwardRef.
// foo.tsimport { Inject, forwardRef } from '@cellularjs/di';import { Bar } from './'
export class FooWithForwardRef { constructor( @Inject(forwardRef(() => Bar)) public bar: Bar, ) { }}
// bar.tsimport './foo';
export class Bar { }
// index.tsexport * from 'foo';export * from 'bar';
// somewhere.tsimport { Container } from '@cellularjs/di';import { FooWithForwardRef, Bar } from 'index';
const container = new Container();(async () => { await container.addProviders([ { token: Bar, useClass: Bar }, { token: FooWithForwardRef, useClass: FooWithForwardRef }, ]);
await container.resolve(FooWithForwardRef); // work well})();
Example: forwardRef can work with useFunc
provider as well.
import { Container, forwardRef } from '@cellularjs/di';
(async () => { const container = new Container(); await container.addProvider({ token: Foo, useFunc: bar => bar, deps: [forwardRef(() => Bar)], })})();
Example: returned value from forwardRef's callback will be treated as a provider token.
import { Container, forwardRef } from '@cellularjs/di';
(async () => { const container = new Container(); await container.addProviders([ { token: 'foo', useValue: 'this is foo' }), { token: 'bar', useFunc: value => value, deps: [forwardRef(() => 'foo')] }, ]);
await container.resolve('bar'); // 'this is foo'})();
#
3. ProxyProxy help you modified resolved value.
Eg 1: create Validate
decorator with the help of proxy.
In this example, we use fastest-validator-decorators
, you can use class-validator
, it is very similar.
yarn add fastest-validator-decoratorsyarn add class-transformer
import { Schema, String, validate } from 'fastest-validator-decorators';import { plainToInstance } from 'class-transformer';import { addProxy, ProxyHandler, ProxyContext, Injectable } from '@cellularjs/di';
class ValidateProxy implements ProxyHandler { constructor( private irq: IRQ, private ctx: ProxyContext, ) {}
async handle() { const { irq, ctx } = this; const dto = plainToInstance(ctx.token, irq.body);
const errors = await validate(dto);
if (errors !== true) { ctx.meta.onError(errors); return; }
return dto; }}
const Validate = () => aClass => { const onError = errors => { throw new IRS({ status: 400 }, { errors }); }
addProxy(aClass, { proxy: ValidateProxy, meta: { onError } });
// fastest-validator-decorators Schema({ strict: true })(aClass);
// allow to detect this class as a provider while scanning Injectable()(aClass);
return aClass;};
//@Validate()export class SignInReq { @String({ length: 8 }) usr: string;
@String({ length: 8 }) pwd: string;}