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

Support Lifecycle.ContainerChildScoped scope #203

Open
TimoGlastra opened this issue Jun 22, 2022 · 2 comments
Open

Support Lifecycle.ContainerChildScoped scope #203

TimoGlastra opened this issue Jun 22, 2022 · 2 comments

Comments

@TimoGlastra
Copy link

TimoGlastra commented Jun 22, 2022

When working with tsyringe I've found the need for a scope that would allow me to use the same instance for a parent + all their childs. This is already possible by registering a singleton on a container, which will then also be used for all child containers.

The problem I'm facing with this is that I would need to manually register all classes inside the child container as a singleton to not share the instances through the root container. If I would define them with @scoped(Lifecycle.ContainerScoped) it would not work with the child containers, if I would define them with @singleton() the instances would be shared across agents.

Description

Not 100% sure what needs to happen, but by testing a bit locally I was able to use @singleton() and manually contstructing new InternalDependencyContainer containers. This is not exposed, but just to get it working as I would like it to work.

const newContainer1 = new InternalDependencyContainer();
const newContainer2 = new InternalDependencyContainer();

This way I have two containers that are always separated, but because the classes are defined as singleton, they will share the containers with their children.

Maybe we can add a way to create a new parent and decorate classes with @scoped(Lifecycle.ContainerChildScoped)?

Alternate solutions

An alternate solution would be to expose the InternalDependencyContainer class and allow me to use singleton. This way there doesn't really need to be any code changed.

Additional context

@codeBelt
Copy link

I am trying to something similar where I have ProjectOfferingsRepository that is not a singleton but I want it to act like a singleton when I need to share it between a parent and child stores.

@injectable()
class ProjectOfferingsRepository {}

@injectable()
class ParentPageStore {
  children: ChildStore[] = [];

  constructor(private _projectOfferingsRepository: ProjectOfferingsRepository) {}
}

@injectable()
class ChildStore {
  constructor(private _projectOfferingsRepository: ProjectOfferingsRepository) {}
}

I thought about create a child container so I could make ProjectOfferingsRepository a singleton to share between the parent and child store but I don't know how to have my stores to use the createChildContainer version and not the globalContainer version?

const parentPageStoreContainer = container.createChildContainer();

parentPageStoreContainer.registerSingleton(ProjectOfferingsRepository);

Is there a way to have the store inject the injectables from the createChildContainer version? Something like a @useContainer which is not a thing but just thought of it. I don't know what the correct solution is but it would be nice to be able to share instances.

@useContainer(parentPageStoreContainer)
@injectable()
class ParentPageStore {
  children: ChildStore[] = [];

  constructor(private _projectOfferingsRepository: ProjectOfferingsRepository) {}
}

@useContainer(parentPageStoreContainer)
@injectable()
class ChildStore {
  constructor(private _projectOfferingsRepository: ProjectOfferingsRepository) {}
}

@codeBelt
Copy link

codeBelt commented Aug 24, 2022

My question above is still valid but this what I did in the meantime until there is a different/better solution.

I am open to suggestion on how to make this better.

@injectable()
class ProjectOfferingsRepository {}

@injectable()
class RateCardRolesRepository {}

/**
 * Some page store have children store(s) and they need to have access to the same repository(s),
 * so we create a child D.I. container that holds on to a singleton version of those repository(s).
 *
 * We should clean up the child D.I. container when we leave the page {@see ParentPageStore.destroy}
 */
const parentPageStoreContainer: IDependencyContainer<
  ProjectOfferingsRepository | RateCardRolesRepository
> = container
  .createChildContainer()
  .registerSingleton(ProjectOfferingsRepository)
  .registerSingleton(RateCardRolesRepository);

@injectable()
class ParentPageStore {
  private readonly _projectOfferingsRepository = parentPageStoreContainer.resolve(ProjectOfferingsRepository);
  private readonly _rateCardRolesRepository = parentPageStoreContainer.resolve(RateCardRolesRepository);

  children: ChildStore[] = [];

  constructor() {}

  destroy(): void {
    parentPageStoreContainer.clearInstances();
  }
}

@injectable()
class ChildStore {
  private readonly _projectOfferingsRepository = parentPageStoreContainer.resolve(ProjectOfferingsRepository);
  private readonly _rateCardRolesRepository = parentPageStoreContainer.resolve(RateCardRolesRepository);

  constructor() {}
}

I also created an interface so I could provide extra type safety with working with child containers. Also, I am open to suggestion on how to make this better.

/**
 * Created this interface, to enforce the correct types because the 'DependencyContainer' interface in
 * `tsyringe` is not a generic.
 */
export default interface IDependencyContainer<U> extends Omit<DependencyContainer, 'resolve'> {
  resolve<T extends U>(token: InjectionToken<T>): T;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants