Open willieseabrook opened 3 years ago
Yeah, this has come up a time or two. Both Inversify and Angular implement it as well. I have been reticent to follow suit, because one thing I have always liked about DI is that there is a guarantee that either you get all of your parameters or you fail to resolve. Still, if people are performing workarounds like you have above, it seems sensible to allow users to opt into an optional resolution.
I'd at least look at a PR for this.
I have extended @willieseabrook solution by introducing a custom decorator
export function injectOptional(token:string) {
return injectWithTransform(Optional, OptionalTransform, token);
}
Now we can use this decorator inside the constructors
constructor(
@inject('MyRequiredService') runtime: MyRequiredService,
@injectOptional('MyOptionalService') config?: MyOptionalService,
) {
I came up with this solution to overcome the coupling with the root container abusing factory providers a little bit:
Full example:
container.register("TransientContainer", { useFactory: (c) => c });
@injectable()
class Optional {
constructor(
@inject("TransientContainer")
private readonly container: DependencyContainer,
) {}
getOptionalValue(token: InjectionToken): unknown {
try {
return this.container.resolve(token);
} catch (e) {
return null;
}
}
}
class OptionalTransform {
public transform(optional: Optional, token: InjectionToken): unknown {
return optional.getOptionalValue(token);
}
}
function injectOptional(token: InjectionToken) {
return injectWithTransform(Optional, OptionalTransform, token);
}
const childA = container.createChildContainer();
const childB = container.createChildContainer();
childA.register("Bar", { useValue: "my bar" });
const barA = childA.resolve(Foo).bar; // my bar!
const barB = childB.resolve(Foo).bar; // null
I am a little wary of the implications of calling resolve
during another resolve chain but so far seems to work ok.
+1 Would really like this to be an official feature. But will take inspiration from the great work-arounds posted above in the meantime :)
Is your feature request related to a problem? Please describe.
I'm about 2 days in to using tsyringe so I might be totally lost, but I can't see how to support optional constructor parameters out of the box.
If you look at the constructor above, config? is optional, and this is something the logic of my class expects.
However, if I do not register 'MyOptionalService', tsyringe will throw an error that it could not find a registration for 'MyOptionalService'
The logic of my code is that sometimes 'MyOptionalService' would be registered, sometimes it would not be registered.
Alternate solutions
My workaround is to hack with @injectWithTransform.
This is verbose.
And will (I think) only work with the global container, where as I am using child containers.
Then:
Proposed Solution
Add @injectOptional() so that the following would work:
@injectOptional would not throw an error if the 'MyOptionalService' is not registered, instead it would simply inject undefined.
Additional context
Inversify supports this feature. Just because that supports it doesn't mean tsyringe should, but it is an example implementation of how other people have approached this feature.
https://github.com/inversify/InversifyJS/blob/master/wiki/optional_dependencies.md