Dependency Injection Cat
DI Cat is a truly clean DI-container, which allows you not to pollute your business logic with decorators from DI/IOC libraries!
Example
export class ApplicationContext extends CatContext<IBeans> {
useCase = Bean<IUseCase>(UseCase)
mobxRepository = Bean<IRepository>(MobxRepository)
}
export class UseCase implements IUseCase {
constructor(
private repository: IRepository,
) {}
makeBusinessLogic() {}
}
export const UIComponent: React.FC = () => {
const appContext = container.getContext<IBeans>({name: 'ApplicationContext'});
const {useCase} = appContext.getBeans();
return (
<button onClick={useCase.makeBusinessLogic}> Click me! </button>
)
}
Installation
Yarn
yarn add dependency-injection-cat
NPM
npm install dependency-injection-cat
Requirements for TSconfig
//tsconfig.json
{
"compilerOptions": {
"baseUrl": "Base url should be specified!"
}
}
Webpack + Babel
const DiCatWebpackPlugin = require('dependency-injection-cat/plugins/webpack').default;
const DiCatBabelTransformer = require('dependency-injection-cat/transformers/babel');
module.exports = {
module: {
rules: [
{
test: /\.(t|j)sx?$/,
loader: 'babel-loader',
options: {
cacheDirectory: false,
plugins: [
[
DiCatBabelTransformer,
{
}
]
]
}
}
]
},
plugins: [
new DiCatWebpackPlugin(),
]
}
//tsconfig.json
{
"compilerOptions": {
//Should be specified
"baseUrl": "your base url"
}
}
Webpack + TS-Loader
const DiCatWebpackPlugin = require('dependency-injection-cat/plugins/webpack').default;
const diCatTransformer = require('dependency-injection-cat/transformers/typescript').default;
module.exports = {
module: {
rules: [
{
test: /\.(t|j)sx?$/,
use: {
loader: 'ts-loader',
options: {
getCustomTransformers: (program) => ({
before: [diCatTransformer(program, {
})],
}),
}
}
}
]
},
plugins: [
new DiCatWebpackPlugin(),
]
}
//tsconfig.json
{
"compilerOptions": {
//Should be specified
"baseUrl": "your base url"
}
}
Webpack + TS-Loader + ttypescript
const DiCatWebpackPlugin = require('dependency-injection-cat/plugins/webpack').default;
module.exports = {
module: {
rules: [
{
test: /\.(t|j)sx?$/,
use: {
loader: 'ts-loader',
options: {
compiler: 'ttypescript'
}
}
}
]
},
plugins: [
new DiCatWebpackPlugin(),
]
}
//tsconfig.json
{
"compilerOptions": {
//Should be specified
"baseUrl": "your base url",
"plugins": [
{
"transform": "dependency-injection-cat/transformers/typescript",
//Here is configuration options, see below
}
]
}
}
TTypescript
//tsconfig.json
{
"compilerOptions": {
//Should be specified
"baseUrl": "your base url",
"plugins": [
{
"transform": "dependency-injection-cat/transformers/typescript",
//Here is configuration options, see below
},
{
"transform": "dependency-injection-cat/plugins/typescript/ReportErrorsTypescriptPlugin"
}
]
}
}
Configuration options
{
diConfigPattern: string | undefined;
ignorePatterns: Array<string> | undefined;
disableLogoPrint: boolean | undefined;
}
Hot Reload
Currently, correct hot reload supported only with DI-Cat Webpack plugin, and for non-global CatContexts.
Known issues:
- When using webpack+babel - webpack or babel don't trigger rebuild of files where defined only types/interfaces,
...etc. Because of this issue, when adding new values to the TBean interface, context will not recompile, and missing
required beans will not be reported. To solve this issue, just make some changes in file where CatContext are defined,
it will trigger recompilation.
CatContext
CatContext it's a place, where you should define your Beans
class CatContext<TBeans, TConfig = null> {}
Rules
-
Name of context should be unique
-
Name of context "Global" is preserved for DI container, see GlobalCatContext
TBeans
TBeans is an interface, of Beans that will be given out of context. Should be the same as in container access calls.
Rules
-
Should be a plain interface without extending, should not have indexed signatures
-
Should be placed in your source files, not from node_modules
-
Bean name should not be getBean or getBeans, it's reserved names for di-container
export interface IBeans {
useCase: IUseCase;
}
TConfig
If you need to pass additional parameters to your classes, for example ID, or something else, you should specify a type
for TConfig (default is null)
export interface IConfig {
id: string;
}
class ApplicationContext extends CatContext<IBeans, IConfig> {
@Bean
useCase(): IUseCase {
const {id} = this.config;
return new UseCase(id);
}
}
GlobalCatContext
Sometimes you want to describe common dependencies that will be used all across your application, for these purposes you
should use GlobalCatContext.
Rules
- You can not get beans from the Global Context using container access
Syntax and usage
export class GlobalApplicationContext extends GlobalCatContext {
@Bean
logger(): ILogger {
return new Logger();
}
}
export class AppContext extends CatContext<IBeans> {
@Bean
useCase(
logger: ILogger
): IUseCase {
return new UseCase(logger)
}
}
Container
Using container you can control your contexts. You can't access container inside files where declared contexts.
Rules
- Name of context in container access calls, should not be "Global", it's reserved for the GlobalCatContext
TBeans
TBeans is an interface, of Beans that will be given out of context. Should be the same as in CatContext.
initContext
InitContext creates instance of context by key (if specified). Also you can pass config to your context using
initContext.
container.initContext<TBeans, TConfig>({
key? : any,
name: string,
config? : TConfig,
});
initContext usage
import { container } from 'dependency-injection-cat';
import { IBeans } from './IBeans';
const applicationContext = container.initContext<IBeans>({name: 'ApplicationContext'});
import { container } from 'dependency-injection-cat';
import { IBeans } from './IBeans';
import { IConfig } from './IConfig';
const applicationContextBySomeKey = container.initContext<IBeans, IConfig>({
name: 'ApplicationContext',
key: 'userId',
config: {userId: 'userId'},
});
getContext
getContext returns instance of context by key (if specified). Can be used only after initialization of context, or
error will be thrown.
container.getContext<TBeans>({
key? : any,
name: string,
});
getContext usage
import { container } from 'dependency-injection-cat';
const context = container.getContext<TBeans>({name: 'ApplicationContext'});
import { container } from 'dependency-injection-cat';
const context = container.getContext<TBeans>({
name: 'ApplicationContext',
key: 'userId',
});
getOrInitContext
GetOrInitContext return instance of context by key (if it was previously initialized), if wasn't - will be
created and returned a new instance of context.
container.getOrInitContext<TBeans, TConfig>({
key? : any,
name: string,
config? : TConfig,
});
getOrInitContext usage
import { container } from 'dependency-injection-cat';
import { IBeans } from './IBeans';
const applicationContext = container.getOrInitContext<IBeans>({name: 'ApplicationContext'});
import { container } from 'dependency-injection-cat';
import { IBeans } from './IBeans';
import { IConfig } from './IConfig';
const applicationContextBySomeKey = container.getOrInitContext<IBeans, IConfig>({
name: 'ApplicationContext',
key: 'userId',
config: {userId: 'userId'},
});
clearContext
clearContext should be used to clear instances of Beans. Can be used, for example when un-mounting components.
container.clearContext({
name: string,
key? : any,
})
clearContext usage
import { container } from 'dependency-injection-cat';
container.clearContext({name: 'ApplicationContext'});
import { container } from 'dependency-injection-cat';
container.clearContext({
name: 'ApplicationContext',
key: 'userId',
});
Bean
A Bean is an object that is instantiated, assembled, and managed by IOC container (Definition from Spring Framework documentation)
Beans can have dependencies, and they also should be defined as a beans or passed manually.
Bean configuration
interface IBeanConfiguration {
scope?: 'prototype' | 'singleton';
}
Bean features
- Detect cyclic dependencies at compile time
- Detect missing Beans at compile time
Property Bean
Property Beans resolving class dependencies automatically from the constructor of a class that passed to Bean.
Rules
- Each class constructor parameter should be defined as a Bean in the Context
- Each class constructor parameter should have a type, type should not be primitive. To pass primitive values to the
constructor, check out Method Beans
Syntax
export class ApplicationContext extends CatContext<IBeans> {
useCase = Bean(UseCaseClass);
useCase = Bean<IUseCase>(UseCase);
useCase: IUseCase = Bean(UseCase);
useCase: IUseCase = Bean<IUseCase>(UseCase);
useCase = Bean(UseCaseClass)
useCase = Bean<UseCaseClass>(UseCaseClass)
useCase: UseCaseClass = Bean(UseCaseClass)
useCase: UseCaseClass = Bean<UseCaseClass>(UseCaseClass)
useCase: IUseCase = Bean(UseCase, {scope: 'prototype'});
}
Usage
export class UseCaseDependency implements IUseCaseDependency {
}
export class UseCase implements IUseCase {
constructor(
private useCaseDependency: IUseCaseDependency,
) {
}
}
import { CatContext, Bean } from 'dependency-injection-cat';
export class ApplicationContext extends CatContext<IBeans> {
useCaseDependency = Bean<IUseCaseDependency>(UseCaseDependency);
useCase = Bean<IUseCase>(UseCase);
}
Method Bean
Method Beans are more flexible, for example it allows you to pass configuration values to your class constructor.
Rules
- Method Bean should always have return type, and it should not be primitive (string, number...). To register Bean with
primitive type you should use type alias.
Syntax and Usage
export class ApplicationContext extends CatContext<IBeans> {
@Bean
useCase(): IUseCase {
return new UseCase();
}
dependency: IUseCaseDependency = Bean(UseCaseDependency);
@Bean
useCase(
useCaseDependency: IUseCaseDependency,
): IUseCase {
return new UseCase(useCaseDependency);
}
@Bean({scope: 'prototype'})
useCase(): IUseCase {
return new UseCase();
}
}
Qualifier
Qualifier needed, when you have 2 or more Beans in the Context with same type. By default, the qualifier is the
name of the parameter in the class constructor, or in the Method Bean`
Also, you can use qualifier, when injecting Beans from GlobalCatContext
Rules
- @Qualifier decorator can be used only in Method Bean
- Argument passed to Qualifier should be a string literal and should not be an empty string
- Qualifier should be a name of Bean (class property name, or method name) defined in current Context
Syntax
export class ApplicationContext extends CatContext<IBeans> {
httpRequester: IRequester = Bean(HttpRequester);
graphQLRequester: IRequester = Bean(GraphQLRequester);
@Bean
useCase(
graphQLRequester: IRequester,
): IUseCase {
return new UseCase(graphQLRequester);
}
}
export class ApplicationContext extends CatContext<IBeans> {
httpRequester: IRequester = Bean(HttpRequester);
graphQLRequester: IRequester = Bean(GraphQLRequester);
@Bean
useCase(
@Qualifier('graphQLRequester') requester: IRequester,
): IUseCase {
return new UseCase(requester);
}
}
export class GlobalApplicationContext extends GlobalCatContext {
graphQLRequester: IRequester = Bean(GraphQLRequester);
httpRequester: IRequester = Bean(HttpRequester);
}
export class ApplicationContext extends CatContext<IBeans> {
@Bean
useCase(
@Qualifier('graphQLRequester') requester: IRequester,
): IUseCase {
return new UseCase(requester);
}
}