Skip to main content

Basic usage

1. Provider#

Provider describe how to resolve a dependency. Currently, DI has 5 types of provider:

1.1. useValue#

Provides 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. useFunc#

useFunc 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. useClass#

Creates 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. useModule#

With 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. useExisting#

useExisting 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 dependency#

note

For now, @cellularjs/di only allow to inject dependency via constructor.

2.1. Injectable#

Injectable 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. Inject#

When 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. forwardRef#

Example: 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. Proxy#

Proxy 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;}