Tsyringe's `resolve` function throws "TypeInfo not known for X" error when using register methods with dependencies

3.1k Views Asked by At

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.

0

There are 0 best solutions below