A dependency injection library for TypeScript. With full support for custom injectors using promises.
yarn add @technove/inject reflect-metadata
Or on npm:
npm install --save @technove/inject reflect-metadata
Then at the top of your application's entry point, add the following line:
import "reflect-metadata";
And finally in your tsconfig.json
, set experimentalDecorators
and emitDecoratorMetadata
to true
.
import { get, Service } from "@technove/inject";
class MyLogger {
log(message: string) {
console.log(message);
}
}
const logger = get(MyLogger); // when you need to get the service
logger.log("Hello, World!"); // -> "Hello, World!"
// the instance will always be the same for repeated calls
const logger2 = get(MyLogger);
logger === logger2; // true!
A popular use-case for dependency injection is to define a different implementation for each environment. This is possible by declaring an abstract class, then extending it for each environment.
import { get, register, Service } from "@technove/inject";
// logger.ts
abstract class Logger {
abstract log(message: string): void;
}
// application.ts
class ProductionLogger extends Logger {
log(message: string) {
console.log("PROD:", message);
}
}
register(ProductionLogger);
// test.ts
class TestingLogger extends Logger {
log(message: string) {
console.log("TEST:", message);
}
}
register(TestingLogger);
// whenever you need a logger
const logger = get(Logger);
logger.log("Hello, World!");
Often you'll create a service that depends on another service. This is very easy to use, and is very flexible.
import { get, Service } from "@technove/inject";
class Logger {
log(message: string) {
console.log(message);
}
}
class MyService {
@Inject()
private readonly logger!: Logger; // when you call get(MyService) the first time, this value will be filled
logMessage() {
this.logger.log("Hello, World!");
}
}
get(MyService).logMessage(); // -> "Hello, World!"
Sometimes you may want to create a custom provider. This is made very simple!
import { get, FieldProvider, Inject } from "@technove/inject";
const provider: FieldProvider = () => "Hello, World!";
class MyService {
@Inject(provider)
private readonly message!: string;
logMessage() {
console.log(this.message);
}
}
get(MyService).logMessage(); // -> Hello, World!
These providers have access to the container, so they can even access other services!
In fact, when you don't give a provider this is nearly equivalent what the default @Inject()
uses:
const provider: FieldProvider = (container: Container) => container.get(Logger);
If you take arguments, you can make this fairly powerful. Say you had a filesystem service, you could use it to read files and inject it into your service:
const ReadFile = (path: string) => (container: Container) =>
container.get(FileSystem).readFile(path);
class MyService {
@Inject(ReadFile("myfile.json"))
private readonly data!: string;
}
This idea is taken even further once you add promises to it.
FieldProvider
s can be marked async and return promised values.
const ReadJson = (path: string) => async () => {
const contents = await readFile(path, "utf-8");
return JSON.parse(contents);
};
class MyService {
@Inject(ReadFile("myfile.json"))
private readonly data!: any;
}
// NOTE:
// using `get(MyService)` will throw an error, because you cannot load services with async values using it.
// instead use the following:
await load(MyService).data; // has data from myfile.json!
MIT