-
Notifications
You must be signed in to change notification settings - Fork 24.8k
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
First-class support for dependency decoration #40141
Comments
Let me see if I understand. You would like to have a hook which runs after an injectable is created but before it is handed to whomever it is requesting. The hook can mutate the injectable instances (or ever replace it). Am I understanding it correctly? NOTE: if we were to add such a feature it would be for Ivy only as VE is deprecated and will be removed soon. |
@mhevery : Yes, that's correct. I should add that I'd also like to be able to use the hook to create new objects if one doesn't exist (ie no provider currently exists in an ancestor element injector, so decorator input is undefined, and the hook can create + return one in that case). If you think that aspect of this feature is a bad idea, then a factory function with `SkipSelf | Optional' can be used. |
If the hook mutated the injectable, that would also affect other places where it is injected, so this essentially performs a side effect that propagates up, down and laterally, doesn't it?
I find it hard to imagine that this benefits enough people to offset the cost when an empty state template for ngFor doesn't, but don't mind my cynicism, I more than welcome additions to the DI since it's one of the benefits Angular has over other frameworks. |
@Airblader : I don't see this proposal causing a new type of side-effects that could be problematic. Today any object receiving an injectable can mutate that injectable, causing the same. If you don't want that, make it immutable. In the case where the decorator replaces the injectable, the replacement should take effect in the injector where the decorator is defined. So, the replacement would only be available in the view tree (for element injectors), or in the module scope (for module injectors). |
@johncrim Hello! Actually with Angular's version compatible with Ivy (from 9/10 version, I don't remember) you can do it using function useDecorator(provider: Type, decorate: (toDecoration: any, ...deps: any[]) => any, deps: any[]) {
return {
provide: provider,
useFactory(...args: any[]) {
// provider must be annotate by @Injectable() decorator so it works only with class provider
// for useValue, useFactory, useExisting I think that currently is not possible, because
// creating new provider by this helper we override old implementation for `provider` (argument of this function) token
if (provider.hasOwnProperty("ɵfac")) {
const toDecoration = provider["ɵfac"]();
return decorate(toDecoration, ...args);
} else {
throw new Error(`${provider.name} is not injectable!`);
}
},
deps,
}
} You can read this comment why this works: #13854 (comment) and in this comment you have also a example how to use existing provider from parent injector or create new one. I think that EDIT: I have an idea to handle also |
Thanks for the comment @magicmatatjahu . How would |
@johncrim No problem.
Angular in compilation time (in JIT, and also in AOT mode) create from decorators like EDIT: If Angular won't find @Injectable()
class Service {
constructor(@Optional() readonly service: OptionalService|null) {}
} by Angular's compilator is changed to (compilator removes decorators and create from them static property): class Service {
constructor(readonly service: OptionalService|null) {}
static ɵfac = () => new Service(ɵɵinject(OptionalService, InjectFlags.Optional));
}
Additionally. We cannot use I know that it's a complicated but it works 😄 Let me know that everything works in your use case. |
Just a heads up that we kicked off a community voting process for your feature request. There are 20 days until the voting process ends. Find more details about Angular's feature request process in our documentation. |
Thank you for submitting your feature request! Looks like during the polling process it didn't collect a sufficient number of votes to move to the next stage. We want to keep Angular rich and ergonomic and at the same time be mindful about its scope and learning journey. If you think your request could live outside Angular's scope, we'd encourage you to collaborate with the community on publishing it as an open source package. You can find more details about the feature request process in our documentation. |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
🚀 feature request
Relevant Package
This feature request is for @angular/core
Description
Today it's clunky and verbose (and buggy if using jest + ViewEngine) to implement decoration of provided dependencies. Please consider adding first class support for decoration to the DI system. Doing so would make a number of use-cases cleaner and (probably) more efficient.
To clarify my use of the term "decoration" in this context, I mean: A function that takes an input injectable from an injector, and can either update or replace or wrap the input injectable, and returns an injectable that is used in place of the input injectable.
I do not mean typescript decorators (eg
@MyDecorator()
). Perhaps another term should be used to avoid confusion, but the term decoration seems appropriate as a well-known pattern.Use cases include:
Describe the solution you'd like
I envision this feature working much like provider factory functions today, but adding the input injectable as a factory function argument. Decorator functions would be run after the object is initially created. In DI today,
@SkipSelf()
can be used to obtain an instance from a parent injector, but decorator functions should be able to run in the same injector where the object was created.A provider factory function is specified today using:
A decorator could be specified as follows:
The syntax shouldn't be limited to directives/components, it could also be used in modules.
Configuration use case
In this example,
AnimationManager
is a normal@Injectable
service, which would be created normally (its dependencies injected), and then passed through the decorator function before use.Wrap injectable use case
Provide if absent use case
In this use case, a
PopupController
instance provided by parent directives should be used, but if an instance doesn't exist one is provided by the component.Describe alternatives you've considered
I don't believe there are any good workarounds for decorating module providers, aside from using different injector tokens for the input and decorated output, and using a factory function for the output. If one were to use the same injector token for the input and decorated output, using
InjectFlags.SkipSelf
at the root injector would fail (would reach the null injector), and not usingInjectFlags.SkipSelf
would result an infinite loop.There are 2 alternatives I've used for implementing decorator patterns in the element injector tree:
@SkipSelf()
and provide the dependency to children - this works, but clutters up the directive with a lot of code, and the directive has to inject all the constructor parameters to create the provided dependencies.InjectFlags.SkipSelf
, and returns the decorated version. This is closer to ideal, but is buggy today, due to spotty implementations ofInjectFlags.SkipSelf
.Note that neither of these provide exactly what I'd like, as both use
SkipSelf
. As noted above, it would be preferable to be able to decorate an object without obtaining the decorator input from a parent injector.Here's an example showing the first alternative (inject and provide the dependency in a directive):
This example component provides 2 objects to its children (a FocusArea and PopupController), if they aren't provided by ancestors. I've cut out all the code related to what the component does. Note that this works, but it adds a lot of unnecessary complexity to the component. All the dependencies needed to instantiate the FocusArea and PopupController wouldn't otherwise be needed in the component.
For the second alternative, I was able to create a more reusable method for providing missing dependencies (one of the decorator use-cases), but as described below it's problematic. Here's the logic for creating missing dependencies (returns a
FactoryProvider
):And here's an equivalent example to the first showing how it can be used:
This is pretty close to what I'd like to see, but one issue is that it only works in Ivy (
inject()
fails in ViewEngine, andInjectFlags.SkipSelf
is not currently supported by node injector ). So, I'm not able to use our jest tests with it (for now) because jest compiles to ViewEngine (today).Even though this second alternative is close, and is almost viable, I believe this is a feature worth adding, because it would cover all the use-cases.
Other comments
I think the expressiveness alone of something like
useDecorator
, given how frequently the pattern is used, would be nice for many code bases. It would also be very handy for test code. I acknowledge thatprovide: (token)
may not be appropriate naming for decoration, I'm on the fence about that.My guess is that first class support for dependency decoration could be more efficient than putting it all in factory functions, because it could be (partly or mostly) wired up by the compiler, reducing runtime code.
I suspect that it would enable many apps to be written with less code, enough to more than offset the addition to @angular/core.
The text was updated successfully, but these errors were encountered: