typedi
typedi copied to clipboard
question: <ServiceNotFoundError: Service with "MaybeConstructable<ApplicationContext>" identifier was not found in the container.>
Description
I updated the typedi version in my project and now I'm getting this error:
ServiceNotFoundError: Service with "MaybeConstructable<ApplicationContext>" identifier was not found in the container. Register it before usage via explicitly calling the "Container.set" function or using the "@Service()" decorator.
My old version: "typedi": "^0.8.0", My current version: "typedi": "^0.10.0",
ApplicationContext
import { Service, Inject, Container } from 'typedi';
import { UserDomain } from 'src/domain/UserDomain';
import { KeycloackService } from 'src/infra/service/internal/KeycloackInternalService';
@Service('ApplicationContext')
export class ApplicationContext {
private user: UserDomain;
private accessToken: string;
private static instance: ApplicationContext;
constructor(
@Inject(() => KeycloackService) private keycloackService: KeycloackService,
) {}
public getApplicationContext(): ApplicationContext {
if (!ApplicationContext.instance) {
ApplicationContext.instance = new ApplicationContext(
Container.get(KeycloackService),
);
}
return ApplicationContext.instance;
}
public setAccessToken(token: string) {
this.accessToken = token;
}
public getAccessToken() {
return this.accessToken;
}
public async setUserLoggedInfo() {
this.user = await this.keycloackService.getUserInfo(this.accessToken);
}
public async getUserInfo(): Promise<UserDomain> {
return this.user;
}
}
Method that consumes the application context
import 'reflect-metadata';
import { Container } from 'typedi';
import { Roles } from 'src/commons/enums/Roles';
import { ApplicationContext } from 'src/app/context/ApplicationContext';
import { UserDomain } from 'src/domain/UserDomain';
export default function Authorize(allowedRolles: Array<Roles>) {
const applicationContext: ApplicationContext =
Container.get(ApplicationContext);
return function (
_target: any,
_propertyKey: string,
descriptor: PropertyDescriptor,
) {
const { value } = descriptor;
descriptor.value = async function (...args: any) {
const userDomain: UserDomain = await applicationContext
.getApplicationContext()
.getUserInfo();
userDomain.checkPermission(allowedRolles);
return value.apply(this, args);
};
};
}
@matheusriios You have not called Container.set
before trying to call Container.get
. Please refer to the changelog: https://github.com/typestack/typedi/blob/develop/CHANGELOG.md#breaking-changes-2
I have the same issue as @matheusriios described.
Wasn't the point of the Service()
decorator to not having to call Container.get
?
@FloSchutz I assume you meant having to call set. I’m afk now so I cannot test it but you might be right. My second guess would be that tokens are provided for each service in the example but the class type is used when trying to retrieve them.
I have the same issue as @matheusriios described.
Wasn't the point of the
Service()
decorator to not having to callContainer.get
?
I must say I agree - this is a rather large breaking change to require calling 'set' . My 0.8.0 to 0.10.0 isn't working now as well.
I should add, the @Service is used so the error is invalid:
ie.
ServiceNotFoundError: Service with "MaybeConstructable<DocumentService>" identifier was not found in the container. Register it before usage via explicitly calling the "Container.set" function or using the "@Service()" decorator.
I investigated a little and I might have found the source of the problem.
In version 0.8.0 an instance was created for a given constructable even if it had no @Service()
decorator. This is the only reason your example was working in that version. When you create a service and provide a name or id, that becomes the injection token, so you should only access it by that token. For example:
// myservice.ts
@Service('MyService')
class MyService {
}
// index.ts
const instance = Container.get('MyService');
This fails in both versions, because MyService never actually registers until you use MyService as a value. Also this shows another problem and I think the main idea behind the auto registration process being removed:
import "reflect-metadata";
import { Container } from 'typedi';
import { ServiceA } from './servicea';
console.log(ServiceA); // Dummy usage
const instance: any = Container.get('ServiceA');
const instance2: any = Container.get(ServiceA);
console.log(instance === instance2); // Logs false
Technically, you get two instances because you provide two different injection tokens. So it turns out the "incorrect" usage was keeping your code running.
So the main issue we have: for the @Service()
decorator to execute, the service itself must be used as a value, otherwise it gets omitted from the compiled version (as tsc removes all type related stuff). Also this is the reason why importing the class and using it as the generic parameter doesn't work either.
I'll continue investigating a way to force the class to be included in the compiled result.
Any updates on this issue?
@vikasgarghb the conclusion is basically this: don’t try to cross reference injection tokens (for example strings) and class references when accessing the container. Also make sure to at least import your class that uses injection token for it to register.
I cross into this issue when using tagged services:
interface I {}
@Service({id: 'tag', multiple: true})
class A implements I {}
@Service({id: 'tag', multiple: true})
class B implements I {}
Container.getMany<I>('tag');
This works because it is in the same file.
But as soon as I put classes A
and B
in their own file, this does not work anymore because classes are not loaded, even when using imports.
As proposed above, I can fix it by explicitely loading class doing:
import './I';
import './A';
import './B';
Container.getMany<I>('tag'); // empty
A;
B;
Container.getMany<I>('tag'); // works