Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

question: <ServiceNotFoundError: Service with "MaybeConstructable<ApplicationContext>" identifier was not found in the container.> #636

Open
matheusriios opened this issue Feb 7, 2022 · 9 comments
Labels
type: question Questions about the usage of the library.

Comments

@matheusriios
Copy link

matheusriios commented Feb 7, 2022

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 matheusriios added the type: question Questions about the usage of the library. label Feb 7, 2022
@attilaorosz
Copy link
Member

@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

@floschutz
Copy link

I have the same issue as @matheusriios described.

Wasn't the point of the Service() decorator to not having to call Container.get?

@attilaorosz
Copy link
Member

@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.

@sgentile
Copy link

I have the same issue as @matheusriios described.

Wasn't the point of the Service() decorator to not having to call Container.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.

@sgentile
Copy link

I should add, the @service is used so the error is invalid:

ie.

ServiceNotFoundError: Service with "MaybeConstructable" identifier was not found in the container. Register it before usage via explicitly calling the "Container.set" function or using the "@service()" decorator.

@attilaorosz
Copy link
Member

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.

@vikasgarghb
Copy link

Any updates on this issue?

@attilaorosz
Copy link
Member

@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.

@alcalyn
Copy link

alcalyn commented Oct 20, 2023

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: question Questions about the usage of the library.
Development

No branches or pull requests

6 participants