GuidesDependency item

Dependency Item

Class Item

Class could be a dependency item directly and registered in an injector. In this case, the class is the identifier as well.

class AuthService {}
 
const injector = new Injector([[AuthService]]);

You could also use an IdentifierDecorator as identifier. In this case, the dependency item is an object that implements ClassDependencyItem:

export interface ClassDependencyItem<T> {
  useClass: Ctor<T>;
  lazy?: boolean;
}

An example:

interface IAuthService {}
 
const IAuthService = createIdentifier<IAuthService>("auth");
 
class AuthService implements IAuthService {}
 
const injector = new Injector([
  [IAuthService, { useClass: AuthService, lazy: true }],
]);

Lazy instantiation

You could set true to indicate the inject not to instantiate the dependency item when it is required but when its property or method is actually accessed or CPU is idle:

interface IHttpInterceptor {
  intercept(): void;
}
 
class AuthHttpInterceptor implements IHttpInterceptor {
  intercept(): void {}
}
 
class FileListService {
  constructor(
    @Inject(IHttpInterceptor) private readonly httpI: IHttpInterceptor,
  ) {}
 
  request() {
    this.httIp.intercept();
  }
}
 
const injector = new Injector([
  [FileListService],
  [IHttpInterceptor, { useClass: AuthHttpInterceptor, lazy: true }],
]);

In this case, FileListService depends on AuthHttpInterceptor. When you get FileListService it would be constructed but nor AuthHttpInterceptor.

const fileListService = injector.get(FileListService);

When you access intercept of AuthHttpInterceptor it would be constructed:

fileListService.request();

By postponing instantiation of some dependencies, start-up performance could get optimized.

đź’ˇ

Please be careful to check if there is any side effect in constructors. The application may not work as you expect in that case.

Value item

You could provide an object that implements ValueDependencyItem as a value item:

export interface ValueDependencyItem<T> {
  useValue: T;
}

The value would directly injected into its dependents.

Factory item

The factory item give back the control of instantiation back to you, and it could declare its dependencies like a class item.

It should implements te following interface:

export interface FactoryDependencyItem<T> {
  useFactory: (...deps: any[]) => T;
  deps?: FactoryDep<any>[];
}

An example:

interface I18NNumberTranspiler {
  transpile(num: number): string
}
const I18NNumberTranspiler = createIdentifier<I18NNumberTranspiler>(
  'i18n-number'
)
 
class ChineseNumberTranspiler implements I18NNumberTranspiler {}
class EnglishNumberTranspiler implements I18NNumberTranspiler {}
 
class I18NService {
  isChinese(): boolean
}
 
const injector = new Injector([
  [I18NService],
  [
    I18NNumberTranspiler,
    {
      useFactory: (i18nService: I18NService) => {
        return i18nService.isChinese()
          ? new ChineseNumberTranspiler()
          : new EnglishNumberTranspiler()
      },
      deps: [I18NService]
    },
  ],
]

Existing item

You could use an existing dependency item as a dependency item. In another word, you can give an existing dependency item an alias. In this case, the dependency item is an object that implements ExistingDependencyItem:

export interface ExistingDependencyItem<T> {
  useExisting: DependencyIdentifier<T>;
}

An example:

interface IHttpInterceptor {
  intercept(): void;
}
const IHttpInterceptor = createIdentifier<IHttpInterceptor>("http-interceptor");
 
class AuthHttpInterceptor implements IHttpInterceptor {
  intercept(): void {}
}
 
const INetworkInterceptor = createIdentifier<IHttpInterceptor>(
  "network-interceptor",
);
 
const injector = new Injector([
  [IHttpInterceptor, { useClass: AuthHttpInterceptor, lazy: true }],
  [INetworkInterceptor, { useExisting: IHttpInterceptor }],
]);
 
console.log(
  injector.get(IHttpInterceptor) === injector.get(INetworkInterceptor),
); // true

Async item

Sometimes it is not necessary to get a dependency in a synchronous way. You could use Async Item and import function of webpack to lazy load a dependency. To do that, you should provide an object that implements the following interface:

export interface AsyncDependencyItem<T> {
  useAsync: () => Promise<
    T | Ctor<T> | [DependencyIdentifier<T>, SyncDependencyItem<T>]
  >;
}

useAsync function must return an dependency item except for async item.

An example:

// index.ts
 
interface IReportService {
  report(msg: string): void
}
const IReportService = createIdentifier<IReportService>('report')
 
class OrderService {
  constructor(
    @Inject(IReportService)
    private readonly reportSrvLoader: AsyncHook<IReportService>
  )
 
  public order(): void {
    // ...
    this.reportSrvLoader.whenReady().then((reportSrv) => reportSrv.report(''))
  }
}
 
const injector = new Injector([
  [OrderService],
  [
    IReportService,
    {
      useAsync: () => import('./reportService'),
    },
  ],
])
// reportService.ts
export default class ReportService {
  report(msg: string): void;
}