InversifyJS
InversifyJS copied to clipboard
Can't implement circular dependencies
I need to implement circular dependency. But workarounds in docs don't work for me. I used lazyServiceIdentifie LazyServiceIdentifer, lazyInject but there some errors.
For example:
A injects B, B injects A.
Expected Behavior
A injected in B. B injected in A.
Current Behavior
I got error when use lazyInject
TypeError: Cannot read property 'get' of undefined
at resolve (webpack-internal:///./node_modules/inversify-inject-decorators/lib/decorators.js:30:34)
at AppStore.getter [as http] (webpack-internal:///./node_modules/inversify-inject-decorators/lib/decorators.js:7:47)
at AppStore.remoteFunction (<anonymous>:2:14)
Possible Solution
Steps to Reproduce (for bugs)
- Create two classes marked @injectable
- Create decorators with getDecorators from inversify-inject-decorators
- Use @lazyInject to inject them to each other
Context
I try configure us common service, but to do it i need to use some methods from another service.
Your Environment
- Version used:
- Environment name and version (e.g. Chrome 39, node.js 5.4):
- Operating System and version (desktop or mobile):
- Link to your project:
Stack trace
The same case I wanted to reproduce with @lazyInject but unfortunately, it doesn't work. I have another error but still. Here is a sample of my code:
import { FooService } from "../services/FooService";
import { HelperService } from "../services/HelperService";
export const container = new Container();
export const { lazyInject } = getDecorators(container, false);
import { FooService } from "../services/FooService";
import { HelperService } from "../services/HelperService";
container.load(buildProviderModule());
container.get<FooService>(FooService);
container.get<HelperService>(HelperService);
======================================
@provideSingleton(FooService)
export class FooService {
@lazyInject(HelperService) helperService: HelperService;
constructor() {
setTimeout(() => {
console.log(this.helperService);
}, 2000)
}
}
@provideSingleton(HelperService)
export class HelperService {
@lazyInject(FooService) fooService: FooService;
constructor() {
setTimeout(() => {
console.log(this.fooService);
}, 3000)
}
}
So at runtime, I am getting an error. throw new Error(ERROR_MSGS.NULL_ARGUMENT); Error: NULL argument
It is thrown becouse FooService in the HelperService is null. But interesting thing is that HelperService in the FooService is defined. So I tried to investigate what is the reason for such behavior. And the reason is an order of imports of this services into the main file like app.ts or inversify.config.ts
import { FooService } from "../services/FooService";
import { HelperService } from "../services/HelperService";
Property service is allways undefined in second imported service. I think its not correct behavior of lazyInjection.
Your Environment
"node": "9.11.1"
"inversify": "4.13.0",
"inversify-binding-decorators": "4.0.0",
"inversify-express-utils": "6.1.0",
"inversify-inject-decorators": "^3.1.0",
"inversify-logger-middleware": "3.1.0",
LazyServiceIdentifer is not a tool for implement circular depencies. It is used to reference a injection token that has not been defined yet.
https://github.com/inversify/InversifyJS/blob/master/wiki/circular_dependencies.md
Although, using property injection with LazyServiceIdentifer do have bug(producing wrong error message), which I fixed it in https://github.com/inversify/InversifyJS/pull/1177
Any update on this? This doesn't seem to be fixed. Is there any sort of workaround?
Up, Is this fixed ?
still a problem
any updates ?
any update?
Same issue.
Here is my workaround. Let's say there are two classes Leader
and Follower
. These two classes depend on each other. In the inversify.config.ts
file:
//Leader is the class, ILeader is the interface and TYPES.LEADER is the symbol
container
.bind<ILeader>(TYPES.LEADER)
.to(Leader);
container
.bind<IFollower>(TYPES.FOLLOWER)
.to(Follower)
.inSingletonScope();
Then in class Follower
, manually define a lazyInject
method
class Follower{
private leader:ILeader;
lazyInject(){
this.leader = container.get<ILeader>(TYPES.LEADER);
}
}
Then in your index.ts
or loader.ts
or any file that you believe the container is initiated, call the lazyInject
.
const follower = container.get<IFollower>(TYPES.FOLLWER);
follower.lazyInject();
In my case the problem was in the incorrect usage of the container
itself, and - of course - in the accidental circular dependency. (see: solution at the bottom of this comment)
I had the following structure:
\ services
\ ServiceA
| index.ts
| ServiceA.ts
| ServiceA.contracts.ts
| ServiceA.composable.ts
\ ServiceB
| index.ts
| ServiceB.ts
| ServiceB.contracts.ts
| ServiceB.composable.ts
In the above "graph":
-
services
is just a directory; -
services/Service[A|B]
directory contains source files for theService[A|B]
; -
services/Service[A|B].ts
file contains a declaration of the mainService[A|B]
class; -
services/Service[A|B].contracts.ts
file contains the interfaces, enums, and other contracts used by theService[A|B]
; -
services/Service[A|B].composable.ts
file contains the declaration of the simple composable function that's capable of resolving theService[A|B]
from the IoC container; -
services/Service[A|B]/index.ts
file exports everything from its adjacent (sibling) files.
// Example body of the `services/Service[A|B]/index.ts` file
export * from './Service[A|B].ts'
export * from './Service[A|B].composable.ts' // Memorize this export! 🧠
export * from './Service[A|B].contracts.ts'
// Example body of the `services/Service[A|B]/Service[A|B].composable.ts` file
import { container } from 'src/inversify.config.ts' // Memorize this import! 🧠
import { Service[A|B], Service[A|B]Type } from './Service[A|B]' // `Service[A|B]Type` is a `symbol`
import { IService[A|B] } from './Service[A|B].contracts' // `IService[A|B]` is an interface
/* @example - usage:
* const { ServiceA } = useServiceA() // Easy peasy lemon squeezy, isn't it? 😉
*/
export const useService[A|B] = (): { Service[A|B]: IService[A|B] } => {
const Service[A|B]: IService[A|B] = container.get<IService[A|B]>(Service[A|B]Type)
return { Service[A|B] }
}
Keep in mind all the above information and consider the following situation:
- Somebody wants to use the
ServiceA
. - To do this, they import the
useServiceA
composable fromservices/ServiceA
(a shorthand forservices/ServiceA/index.ts
). - The
useServiceA
composable depends on thecontainer
constant imported from thesrc/inversify.config.ts
, so every time somebody wants to use one of the composables, they implicitly import the wholecontainer
.
Now, let's examine the inversify.config.ts
file:
import 'reflect-metadata'
import { Container } from 'inversify'
import { IServiceA, ServiceA, ServiceAType } from './services/ServiceA' // Memorize this import! 🧠
import { IServiceB, ServiceB, ServiceBType } from './services/ServiceB' // Memorize this import! 🧠
const container = new Container()
container.bind<IServiceA>(ServiceAType).to(ServiceA)
container.bind<IServiceB>(ServiceBType).to(ServiceB)
Read the above file thoroughly, and then consider the following:
- Somebody requires
useServiceA
composable. -
useServiceA
composable requirescontainer
. -
container
requires all of theservices/ServiceA
andservices/ServiceB
, including their composable functions. - Exported composables of course require the
container
again... - ...and so on 🙃
Solution?
In my case, I had to stop using the composable functions and depend directly on the container
constant. It meant that I needed to refactor the convenient const { Service[A|B] } = useService[A|B]()
to a slightly longer const Service[A|B]: IService[A|B] = container.get<IService[A|B]>(Service[A|B]Type)
. This is not only longer, but also requires 2 more imports. However, at the same time, it prevents the circular dependency 😛
I know it's a lot of text, but if you'd like to learn more, feel free to contact me via e-mail. I'll try to explain better face-to-face 🙂 I didn't want to write this piece at all, but I felt like I should contribute to the community, and to pay my share 😝