Dependency Injection
Overview
Odi has powerful dependency injection mechanism out of the box.
For example we have service and class for some calculations. Our goal is to inject those dependencies into @Controller and @Service
Code
Calculation class:
import { define } from "odi";
class DiscountCalculator {
discount: number;
getDiscount(total: number) {
return (this.discount * total) / 100;
}
}
define(DiscountCalculator)
.set('default', { props: { discount: 20 }});Service class:
import { Service, Autowired } from "odi";
import { OrderRepository } from "./order.repository";
import { DiscountCalculator } from "./discounter.ts"
@Service()
export class OrderService {
@Autowired()
repository: OrderRepository;
@Autowired()
discounter: DiscountCalculator;
public getDiscount(orderId: string) {
const order = await this.repository.findOne(orderId);
if(!order)
throw Error(`No order with id - ${orderId}`);
return this.discounter.getDiscount(order.total);
}
}Controller class:
import { Get, Controller, IController, Autowired } from "odi";
import { OrderService } from "./order.service";
@Controller('orders')
export class OrderController extends IController {
@Autowired()
orderService: OrderService;
@Get async '{orderId}' (orderId: string) {
const multiplier = this.getQueryParam('multiplier');
const discount = await this.orderService.getDiscount(orderId);
return multiplier * discount;
}
}Explanations
All dependencies will be automatically injected in proper order. DiscountCalculator component was defined with predefined prop discount , as default instance for injection.
OrderService property discounter will be an instance of DiscountCalculator class and discount value will be 10. By default, DiscountCalculator will be singleton, but this behavior can be changed.
Decorators
There are 2 decorators for DI:
@Autowired(id?: string)- sets method or property which will be provided with dependencies. Have one optional parameter id with default value"default". If you usedefinemethod for configuring instances, autowired will find that instance by passedidfor injection. Note,idwill work only for class properties, methods will ignore it.@Inject(id?: string)- define dependency byidfor parameter. It can be used for constructor and method injection. Ifdefaultinstance is needed, just omit usage of this decorator.
Injection
Currently there are three supported types of injection.
By constructor
By property
By method
Note, in next major release injection by Providers and Factories will be added.
Example of equivalent injection in 3 different ways: Constructor, Property, Method
Constructor
Every argument of constructor will be automatically injected by type. Primitive or unresolved types will be ignored. For more control over the process method injection should be used.
import { Service, Inject } from "odi";
import { FoodManager } from "./food.manager";
import { MedicineManager } from "./medicine.manager";
@Service()
export class PetService {
constructor(
readonly food: FoodManager,
@Inject('pets') readonly medicine: MedicineManager
) {}
}FoodManager and MedicineManager dependencies will be automatically injected. MedicineManager will be injected by pets id.
Property
Only properties that decorated by @Autowired() will be injected.
import { Service, Inject } from "odi";
import { FoodManager } from "./food.manager";
import { MedicineManager } from "./medicine.manager";
@Service()
export class PetService {
@Autowired()
food: FoodManager;
@Autowired('pets'):
medicine: MedicineManager;
}Method
Only properties that decorated by @Autowired() will be injected. But id parameter of @Autowired() will be ignored. For classifying dependencies use @Inject() decorator. Any method can be used for injection.
import { Service, Inject } from "odi";
import { FoodManager } from "./food.manager";
import { MedicineManager } from "./medicine.manager";
@Service()
export class PetService {
@Autowired()
injectManagers(food: FoodManager, @Inject('pets') medicine: MedicineManager ) {
...
}
}Autowired Methods will be called on instance initialization.
Dependencies definition
Definition method is fully typified. Constructor parameters and properties can be manually setted during definition.
Use define function for it
import { define } from "odi";
class Pet {
...
}
define(Pet)
.set('default', {
constructorArgs: [...],
props: {...},
type: 'singleton'
})
.set('special', {
constructorArgs: [...],
props: {...},
type: 'scoped'
});
//Or
define(Pet)
.setSingleton('default', {
constructorArgs: [...],
props: {...}
})
.setScoped('special', {
constructorArgs: [...],
props: {...}
});Other methods (ways) of definition will be added in next major release.
Unlimited instance types can be defined using this way. Each instance is defined by id (this id can be passed into @Autowired and/or @Inject decorators for proper instance type injection).
Shorthands
There are 2 shorthands for omitting type during definition:
setSingleton(id: string, ...) and setScoped(id: string, ...)
Details
Typing for Foo class in define function:
import { define } from "odi";
import { Baz } from "./baz";
class Foo {
foo: number;
lorem: boolean;
constructor(bar: string, baz: Baz, far: boolean) {
...
}
}And define signature with typings for this class:
define(Foo)
.set(id: string, {
type?: 'singleton' | 'scoped',
constructorArgs?: [
(bar| undefined),
(Baz | undefined),
(far| undefined)
],
props?: {
foo?: number,
lorem?: boolean
}
})
.set(...)
...Properties or constructor arguments can be predefined. If you want to predefine only one constructor argument and automatically inject others, just set the value to undefined.
For example, baz must be injected automatically, but bar and far got predefined values:
define(Foo)
.set(id: 'default', {
constructorArgs: ["Hello, world !", undefined, false]
})Args that got value undefined will be automatically injected.
Same thing with props. If property needs to be injected automatically, just omit it
define(Foo)
.set(id: 'default', {
props: {
foo: 2
}
})Typization Preview
props accepts only properties, not methods, so it's fully save. Same thing with constructorArgs. It's strict tuple.
Example in VS Code


Last updated
Was this helpful?