Skip to content

Latest commit

 

History

15 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

!! This repository moved. Please use the new location !!

SonarCloud

Bugs Code Smells Duplicated Lines (%) Lines of Code Maintainability Rating Reliability Rating Security Rating Technical Debt Vulnerabilities

minimal-dependency-injection-container npm version

Minimal DI container, which support:

  • bean registration:
    • new instantiation
    • singleton
    • constant definition
    • factory
  • bean injection by key, type

Setup:

tsconfig.json:

{
  "experimentalDecorators": true,
  "emitDecoratorMetadata": true, 
  "target": "es6"
}

Decorators:

@Injectable() 
@Inject(key: string) // inject class constructor parameters or method parameters
@InjectProperty(key: string) // inject class property
@RunBefore(key: string) // when call a method run before
@RunAfter(key: string) // when call a method run after return
@Setter() // run after class instantification
@InitMethod()  // run after class instantification
@MethodWrapper(key: string)  // wrap the function with a IMethodWrapper class
@FactoryMethod()
@AddTags(tags) //  you can retrieve the entities it from the container with the tags

REGISTER:

//REGISTER
container.register('key', value) //value can be a class definition, constant or a function
// you have some options when register: 
// .asPrototype, .asSingleton .asConstant(), .asFactory(factoryFnName?: string), .withContext(context: {})
container.register('key2', value).asPrototype() //new instance
container.register('key3', value).asSingleton() //as singleton
container.register('key4', value).asSingleton().withContext({'paramKey': 'otherKey', 'paramKey2': 'otherKey2'}) // register with context: @Inject('paramKey') ... it will replace to 'otherKey'
container.register('key4', 'some constant').asConstant() // register constant
container.register('factoryKey', FactoryClass).asFactory();
container.register('factoryResult', String).asFactoryResult('factoryKey');

container.registerTypes([MyClass, Myclass2]) // registrate by type

RESOLVE:

//RESOLVE
const resolveResult = await container.resolve<Type>('key');
const productController = await container.resolveByType<ProductController>(ProductController)

Before - After

Before:

const productController  = new ProductController(new CacheService(new ProductRepository(new Database(connect)), new ScraperService(new ScraperServiceOptions(), true)));

After:

const container = new Container({enableAutoCreate: true});
const productController = await container.resolveByType<ProductController>(ProductController)

After 2:

const container = new Container({enableAutoCreate: true});
container.registerTypes([
  ProductController,
  CacheService,
  ProductRepository,
  Database,
  ScraperService,
  ScraperServiceOptions,
])
const productController = await container.resolveByType<ProductController>(ProductController)

After 3:

const container = new Container();
container.register('ScraperServiceOptions',  ScraperServiceOptions).asSingleton();
container.register('visible', true).asConstant()
container.register('ScraperService', ScraperService).asSingleton();
container.register('connection', connect).asConstant();
container.register('Database', Database).asSingleton();
container.register('ProductRepository', ProductRepository).asSingleton();
container.register('CacheService', CacheService).asSingleton();
container.register('ProductController', ProductController).asSingleton();
const productController = await container.resolve<ProductController>('ProductController');

Or can be combined as you wish.

Simplest usage without registration:

@Injectable
class Something {

    public getValue() {
        return "more";
    }
}

@Injectable
class SomethingElse {
    constructor(private readonly something: Something, private readonly str: string = "pizza") {
    }

    public getValue() {
        return `need ${this.something.getValue()} ${this.str}`;
    }
}

@Injectable
class Client {
    constructor(private readonly something: Something, private readonly somethingElse: SomethingElse) {
    }

    public say() {
        return `I ${this.somethingElse.getValue()} and ${this.something.getValue()} coffee.`;
    }
}

@Injectable
class Service {
    constructor(private readonly client: Client) {
    }

    public check() {
        return `client says: ${this.client.say()}`;
    }
}

// create the container
const container = new Container({enableAutoCreate: true});

// resolve without registration any @Injectable class
const service = await container.resolveByType<Service>(Service);

console.log(service.check());  // it will be: "client says: I need more pizza and more coffee."

If you don't enable enableAutoCreate, the above code will look like this:

    const container = new Container();

    // registrate all dependencies 
    await container.registerTypes([
        Something,
        Client,
        Service,
        SomethingElse
    ]);

    // you can run a coontainer test, it will check all the the dependencies.
    // container.containerTest(); // if something missing throw a error

    const service = await container.resolveByType<Service>(Service);
    console.log(service.check());  // it will be: "client says: I need more pizza and more coffee."

You can specify every injected key:

class Client {
  constructor(@Inject('value') private readonly value: string) {
  }
  public say() {
    return `hello ${this.value}`;
  }
}
class Service {
  constructor(@Inject('client') private readonly client: Client) {}
  public check() {
    return `client says: ${this.client.say()}`;
  }
}
const container: IContainer = new Container();
container.register('value', "world");
container.register('client', Client);
container.register('service', Service);
const service = container.resolve<Service>('service');
console.log(service.check()); // Should write "client says: hello world"

@InjectProperty example

    class Test {
        @InjectProperty('value')
        testProp: any;
        
        @InjectProperty('value2')
        testProp2: any;
        
        constructor() {
        }
    }
    container.register('value', 'happy');
    container.register('value2', 'panda');
    container.register('test', Test);
    const testObj = await container.resolve<Test>('test');
    console.log(`I am a ${testObj.testProp} ${testObj.testProp2}`) // result: I am a happy panda

@Stetter() example

        const container = new Container();
        class SetterTestClass {
            panda: string = "sad panda";
            @Setter()
            set pandaSetter(@Inject('param1') param1: string) {
                this.panda = param1;
            }
            constructor() {
            }
        }
        const param1 = "happy panda"
        container.register('SetterTestClass', SetterTestClass);
        container.register('param1', param1)
        const setterTestClass = await container.resolve<SetterTestClass>('SetterTestClass');
        console.log(setterTestClass.panda) // it should be "happy panda"

@RunAfter, @RunBefore, @MethodWrapper example

!! Currently don't work with "this" ref !!

  • you can use this decorators individually or together
    class Test {
        @MethodWrapper('MyMethodWrapper')
        @RunAfter('MyRunAfter')
        @RunBefore('MyRunBefore')
        testFn(p: string, p2: string) {
            console.log("p = " + p, " p2 = " + p2);
        }
    }
    class MyRunAfter implements IRunAfter {
        run(): void {
            console.log('run this after.')
        }
    }
    class MyRunBefore implements IRunBefore {
        run() {
            console.log("run before.")
        }
    }
    class MyMethodWrapper implements IMethodWrapper {
        run(next: Function, params: any[]): any {
            console.log("wrapper before.")
            next(...params);
            console.log("wrapper after.")
        }
    }
}
    container.register('MyRunBefore', MyRunBefore);
    container.register('MyRunAfter', MyRunAfter);
    container.register('MyMethodWrapper', MyMethodWrapper);
    container.register('test', Test);
    const testObj = await container.resolve<Test>('test');
    testObj.testFn('test', 'test2');
// log result:    
/*
wrapper before.
run before.
p = test  p2 = test2
run this after.
wrapper after.
*/

Factory sample:

export class FactoryClass {
    constructor(@Inject("factoryParam1") private readonly value: string) {
    }
    getValue() {
        return this.value;
    }
    create() {
        return "This is ths FactoryClass create() result."
    }
    @FactoryMethod()
    factoryMethodName(@Inject('factoryMethodKey1') value: string, @Inject('factoryMethodKey2') value2: string) {
        return "factoryMethodName result: value: " + value + " value2: " + value2;
    }
    create2() {
        return "create2 result";
    }
}
const container = new Container();
const firstFactoryParam = "firstFactoryParam";
const secondFactoryParam = "secondFactoryParam";
container.register('factoryParam1', 'factoryParam1 constant data').asConstant()
container.register('factoryMethodKey1', firstFactoryParam).asConstant();
container.register('factoryMethodKey2', secondFactoryParam).asConstant();
container.register('factoryKey', FactoryClass).asFactory()
// if asFactory() param empty call the @FactoryMethod fn, but if it is not exist default is create()
container.register('factoryResult', String).asFactoryResult('factoryKey');
const factoryResult = container.resolve<String>('factoryResult'); // it will be "factoryResult:  factoryMethodName result: value: firstFactoryParam value2: secondFactoryParam"
container.register('otherParam1', 'replaced1').asConstant();
container.register('otherParam2', 'replaced2').asConstant();
container.register('factoryKey2', FactoryClass).asFactory();
container.register('factoryResult2', String).asFactoryResult('factoryKey2').withMethodContext({'factoryMethodKey1': 'otherParam1', 'factoryMethodKey2': 'otherParam2'});
const factoryResult2 = container.resolve<String>('factoryResult2'); // it will be: factoryResult:  factoryMethodName result: value: replaced1 value2: replaced2

About

dependency injection

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages