[go: nahoru, domu]

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

Feat: transient(...) function to complement the inject(...) function #56815

Open
iJungleboy opened this issue Jul 2, 2024 · 2 comments
Open
Labels
area: core Issues related to the framework runtime core: di feature Issue that requests a new feature
Milestone

Comments

@iJungleboy
Copy link
iJungleboy commented Jul 2, 2024

Which @angular/* package(s) are relevant/related to the feature request?

No response

Description

With standalone components comes great responsibility - especially regarding service lifetimes.

The problem is that components will need some services, and it's a lot of guessing which ones have already been added to providers at a level above, which services should start anew (because they have state, which shouldn't be modified by sub-components by accident) and much more.

My experience is that this leads to over-use providers: [...] because it's often a guesswork which providers are available from parent-level components and which ones are net. having everything it the own providers[...] has a smell 💩 to it though.

Basically sub-components could forget their own providers - and everything works - but this will result in them sharing the service by accident.

So it's fine if it's on purpose, but my experience is that it's accidental a lot, leading to difficult to debug issues.

Proposed solution

I propose a transient(...) method which is similar to inject(...), except that it ensures that the service it creates is only created once, and never reused.

This would kind of result in shorter, nicer code

@Component({
  // selector etc.
})
export class Example {
  private = transient(ViewService);
}

...than before (where you could never ensure that sub-components would reuse your service):

@Component({
  // selector etc.
  providers: [
    ViewService,
  ],
})
export class Example {
  private = inject(ViewService);
}

The code for the transient(...) which works looks like this - but I'm sure there's room for improvement:

import { Injector, ProviderToken, TypeProvider, inject } from '@angular/core';

/**
 * Transient dependency injection provider.
 * This will create a new instance of the provided token,
 * without making it available in sub-components
 * as would happen with providers in a component.
 * 
 * @param token the class which is injectable
 * @param injector the injector to use, if not provided, the current injector will be used.
 *     Not not necessary when using transient in the class properties construction or in the constructor,
 *     as the injector will be available in the constructor.
 *     But necessary when using transient inside anything else.
 * @returns 
 */
export function transient<T>(token: ProviderToken<T>, injector?: Injector): T {
  // make sure we have an injector
  // this will throw an error, if transient is used outside of construction and without providing an injector
  injector ??= inject(Injector);

  // create a new injector which is only meant to be used for this transient instance
  const subInjector = Injector.create({
    providers: [
      token as TypeProvider
    ],
    parent: injector
  });

  // return the instance
  return subInjector.get(token, undefined, { self: true });
}

Alternatives considered

I guess we could also

  1. extend the inject-options to provide flags for this as well, but I think this would be simpler and clearer.
  2. Change the signature of services so they could be marked as transient and therefor the injector would always create a new service - this would be a welcome addition anyhow, but my recommendation with the transient(...) injector would still be great, because you would not have to define the providers just to get a service.
@iJungleboy iJungleboy changed the title Feat: transient function to complement the inject function Feat: transient(...) function to complement the inject(...) function Jul 2, 2024
@pkozlowski-opensource pkozlowski-opensource added area: core Issues related to the framework runtime core: di labels Jul 3, 2024
@ngbot ngbot bot added this to the needsTriage milestone Jul 3, 2024
@pkozlowski-opensource pkozlowski-opensource added the feature Issue that requests a new feature label Jul 3, 2024
@ngbot ngbot bot modified the milestones: needsTriage, Backlog Jul 3, 2024
@pkozlowski-opensource pkozlowski-opensource added area: testing Issues related to Angular testing features, such as TestBed and removed feature Issue that requests a new feature area: core Issues related to the framework runtime core: di labels Jul 3, 2024
@ngbot ngbot bot modified the milestones: Backlog, needsTriage Jul 3, 2024
@pkozlowski-opensource
Copy link
Member

We were discussing a similar API for a while now - a facility that would allow us to create an instance of an injectable (with resolving its dependencies from an injector), but without registering the injectable itself in an injector.

Marking is as a feature request - it will need API design, though.

@pkozlowski-opensource pkozlowski-opensource added area: core Issues related to the framework runtime core: di feature Issue that requests a new feature and removed area: testing Issues related to Angular testing features, such as TestBed labels Jul 3, 2024
@ngbot ngbot bot modified the milestones: needsTriage, Backlog Jul 3, 2024
@iJungleboy
Copy link
Author

If I may add another related point: it would be good if services could be marked as transient only, so they are never shared / reused and always re-created for each inject(...)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: core Issues related to the framework runtime core: di feature Issue that requests a new feature
Projects
None yet
Development

No branches or pull requests

2 participants