I was playing with Tsyringe. Seems like it works well when I use decorators however I couldn't make it work with register function.
Here are what I've tried
Test 1: OK
Here I simply use @singleton() decorator, it works fine!
@singleton()
class Child1 {}
@singleton()
class Child2 {}
@singleton()
class Parent {
constructor(public readonly child1: Child1, public readonly child2: Child2) {}
}
const parent = container.resolve(Parent);
expect(parent).toBeInstanceOf(Parent);
expect(parent.child1).toBe(container.resolve(Child1));
expect(parent.child2).toBe(container.resolve(Child2));
Test 2: ERROR
I expected this to be same as previous one. I register via registerSingleton method instead of @singleton() decorator. However container.resolve throws TypeInfo not known for "Parent" error.
class Child1 {}
class Child2 {}
class Parent {
constructor(public readonly child1: Child1, public readonly child2: Child2) {}
}
container.registerSingleton(Child1);
container.registerSingleton(Child2);
container.registerSingleton(Parent);
const parent = container.resolve(Parent);
expect(parent).toBeInstanceOf(Parent);
expect(parent.child1).toBe(container.resolve(Child1));
expect(parent.child2).toBe(container.resolve(Child2));
Test 3: ERROR
I also expected this to be same as the first one. I register via register method instead of @singleton() decorator. Again container.resolve throws TypeInfo not known for "Parent" error.
class Child1 {}
class Child2 {}
class Parent {
constructor(public readonly child1: Child1, public readonly child2: Child2) {}
}
container.register(Child1, { useClass: Child1 }, { lifecycle: Lifecycle.Singleton });
container.register(Child2, { useClass: Child2 }, { lifecycle: Lifecycle.Singleton });
container.register(Parent, { useClass: Parent }, { lifecycle: Lifecycle.Singleton });
const parent = container.resolve(Parent);
expect(parent).toBeInstanceOf(Parent);
expect(parent.child1).toBe(container.resolve(Child1));
expect(parent.child2).toBe(container.resolve(Child2));
Test 4: OK
Then I removed the constructor arguments and used register method. It worked fine too.
class Parent {}
container.register(Parent, { useClass: Parent }, { lifecycle: Lifecycle.Singleton });
const parent1 = container.resolve(Parent);
const parent2 = container.resolve(Parent);
expect(parent1).toBeInstanceOf(Parent);
expect(parent1).toBe(parent2);
Question
What am I doing wrong in tests 2 and 3?
Update
I've checked Tsyringe unit tests. Classes are decorated with either @injectable() or @singleton() in all cases where a constructor injection is tested.
Then I found that @injectable() makes a typeInfo.set(target, getParamInfo(target)); call (also @singleton calls @injectable). As far as I understand, this sets constructor parameters of a decorated class to a global Map called typeInfo. register or registerSingleton are not making this call. Hence when I try to resolve I get a "TypeInfo not found" error.
I'm not sure if this is a bug or a feature.
Update2
As far as I understand, it is neither a bug nor a feature. It is just a limitation. Constructor parameter type resolution in Tsyringe relies on (I think there is nothing else that it could rely on) "Reflect.getMetadata" which can be used only when you use decorators. Hence, a class' dependencies can only be injected if it is decorated by one of Tysringe's decorators such as @injectable(), @singleton() etc.