README
Injector Next
Next gen Dependency Injector. Very similar to Angular injector. Extremely small ~1Kb
WARNING: It was meant to be used with Typescript. While it is possible to use with plain JS it is not advised and will be hard to use.
For Typescript, you need to have these two options turned on in tsconfig.json
:
{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true
}
}
Depends on reflect-metadata
.
WARNING: API is not yet final
Requirements
Technically it should have no requirements.
Installation
For yarn:
yarn add injector-next
For npm:
npm i injector-next
Docs
Imports:
// ES6 JS/Typescript style
import { Injector } from 'injector-next';
// require
const { Injector } = require('injector-next');
Class Decorators:
// Put this before the class you wish to be automatically injected as singleton
@Injectable()
// Alias to Injectable, does the same thing
@Service()
Parameter decorators:
// If you wish to provide your own factory, put this before the parameter
// Example below
@Token({ factory: () => any })
Basic usage:
@Injectable()
class ServiceA {
a = 1;
}
@Injectable()
class ServiceB {
constructor(
// injector automatically figures out the class
// based on parameter type
protected sa: ServiceA
) {}
}
const sb = Injector.resolve(ServiceB);
Manual registration:
class ExternalClass {
a = 1;
}
// Notice that registration requires actual instance
Injector.register('unique-name', new ExternalClass());
// Now you can get it directly by name:
Injector.get('unique-name');
// Or by token itself
Injector.resolve(ExternalClass);
// You can also override it, calling register twice will result in error
// IMPORTANT: this will get rid of original instance
Injector.override('unique-name', new ExternalClass());
Custom resolver:
// External class that you cannot modify
class ServiceExternal {
a = 1;
}
@Injectable()
class ServiceB {
constructor(
protected sa: any,
protected cons: typeof console,
protected dt: Date,
) {}
}
const customResolve = (token: any, idx: string) => {
// Either check by index of parameter
if (+idx === 0) {
// this will make it basically factory that would spawn instance
// each time is resolved
return new ServiceExternal();
}
if (+idx === 1) {
return console;
}
// Or by class itself
if (token === Date) {
return new Date();
}
return null;
};
const sb = Injector.resolve(ServiceB, customResolve);
Result:
ServiceB {
sa: ServiceExternal { a: 1 },
cons: Object [console] {
log: [Function: log],
warn: [Function: warn],
dir: [Function: dir],
time: [Function: time],
timeEnd: [Function: timeEnd],
timeLog: [Function: timeLog],
trace: [Function: trace],
assert: [Function: assert],
clear: [Function: clear],
count: [Function: count],
countReset: [Function: countReset],
group: [Function: group],
groupEnd: [Function: groupEnd],
table: [Function: table],
debug: [Function: debug],
info: [Function: info],
dirxml: [Function: dirxml],
error: [Function: error],
groupCollapsed: [Function: groupCollapsed],
Console: [Function: Console],
profile: [Function: profile],
profileEnd: [Function: profileEnd],
timeStamp: [Function: timeStamp],
context: [Function: context]
},
dt: 2021-07-07T18:35:05.968Z
}
Custom tokens:
// create a factory class
const mapFactory = () => { return new Map(); }
// mark it as di factory (otherwise injector cannot distinguish
// between actual class and factory
mapFactory.diFactory = true;
@Injectable()
class ServiceB {
constructor(
// Mark parameter directly
@Token({ factory: mapFactory }) protected map: Map<any, any>
) {}
}
const sb = Injector.resolve(ServiceB);
Result (map is a factory):
ServiceB { map: Map(0) {} }
Custom token to provide non classes:
// create a factory class
const singletonInstance = {
unnamed: 'object',
that: 'has no constructor'
}
const configFactory = () => { return singletonInstance; }
configFactory.diFactory = true;
@Injectable()
class ServiceB {
constructor(
// Mark parameter directly
@Token({ factory: configFactory })
protected config: any
) {}
}
const sb = Injector.resolve(ServiceB);
Result (config is singleton):
ServiceB { config: { unnamed: 'object', that: 'has no constructor' } }
Injector as factory:
// create a factory class
const randFactory = () => { return Math.random(); }
randFactory.diFactory = true;
@Injectable()
class ServiceX {
stable = Math.random();
}
@Injectable()
class Entity {
constructor(
protected serv: ServiceX,
@Token({ factory: randFactory })
protected rand: number
) {}
}
// Those will be just instanciated but not kept in registry
const e1 = Injector.resolve(Entity, null, true);
const e2 = Injector.resolve(Entity, null, true);
const e3 = Injector.resolve(Entity, null, true);
Result (serv is still a singleton but factory spawned random numbers each time):
Entity {
serv: ServiceX { stable: 0.5692771742563438 },
rand: 0.7034761836358194
}
Entity {
serv: ServiceX { stable: 0.5692771742563438 },
rand: 0.20460451477948371
}
Entity {
serv: ServiceX { stable: 0.5692771742563438 },
rand: 0.22173878210817932
}
Advanced usage:
WARNING modifying design:paramtypes
directly may result in some odd behaviour
if not done right.
interface CustomOptions {
min: number;
max: number;
amount: number;
}
const CustomToken = (options: CustomOptions): ParameterDecorator => {
return (target: Object, propertyKey: string | symbol, parameterIndex: number) => {
// collect existing param types
const tokens = Reflect.getMetadata('design:paramtypes', target) || [];
// make new factory
const factory = () => {
const out = [];
for (let i = 0; i < options.amount; i++) {
out.push(Math.random() * (options.max - options.min) + options.min)
}
return out;
};
// mark it as factory
factory.diFactory = true;
tokens[parameterIndex] = factory;
// redefine param types so we can check where is our namespace suppose to be injected
Reflect.defineMetadata('design:paramtypes', tokens, target);
};
};
@Injectable()
class ServiceB {
constructor(
@CustomToken({ min: 1, max: 10, amount: 3 })
protected arr: number[],
@CustomToken({ min: 10, max: 20, amount: 5 })
protected arr2: number[],
@CustomToken({ min: 100, max: 200, amount: 7 })
protected arr3: number[],
) {}
}
const sb = Injector.resolve(ServiceB);
Result:
ServiceB {
arr: [ 6.682922113497945, 2.1919589707056892, 3.3588555893813377 ],
arr2: [
15.002033575190106,
11.684026606562002,
16.84351565375917,
16.820905407384693,
16.210885789832872
],
arr3: [
166.39310836720702,
130.8052358138108,
185.8191399183811,
164.22233809832989,
131.44972544841204,
186.65161324868083,
110.1801704311695
]
}