InversifyJS
InversifyJS copied to clipboard
Complex contextual resolution (multiple tags/tag and name)
In the system I'm building, we have ViewControllers
and ViewControllerComponents
. Each ViewController type has a name, like form_view
, map_view
, etc. Each ViewController has specific ViewControllerComponents that correspond to it, like TextArea
and Numeric
for form_view
, and Point
and Polygon
for map_view
.
Expected Behavior
ViewControllers
are easy:
context.bind(VIEW_TYPES.ViewController).to(FormViewController).whenTargetNamed("form_view")
And then for the factory I do:
bind<ViewControllerFactory<any>>(VIEW_TYPES.ViewControllerFactory).toFactory<View<any>>(
context => name => context.container.getNamed(VIEW_TYPES.ViewController, name),
);
Now what I want to do is the following for ViewControllerComponents
:
context.bind(VIEW_TYPES.ViewControllerComponent).to(TextArea).whenTargetNamed("text_area").whenTargetTagged("view_type", "form_view")
...
bind<ViewControllerComponentFactory<any>>(VIEW_TYPES.ViewControllerComponentFactory).toFactory<ViewComponent<any>>(
context => view_type => component_name => context.container.getMultiTagged(VIEW_TYPES.ViewControllerComponent,{[TAGS.NAME_TAG]: component_name, "view_type": view_name})
);
There are two problems: the first is that you can't chain bindingToSyntax
(so .whenTargetNamed().whenTargetTagged()
won't work). I solved this with the following complex constraint:
const constraint: interfaces.ConstraintFunction = (request: interfaces.Request | null) =>
request !== null && request.target !== null && (
request.target.matchesTag("view_type")(view_type)
) && (
names.map(name => request.target.matchesNamedTag(name)).some(i=>i) // check that any array element is truthy
);
But I wasn't able to figure out how to craft a request that can satisfy this request.
Current Behavior
I dug deeper here and found that inside of the plan
function in planner.ts
, a Target
is created, but you're only allowed to specify a single key for its Metadata
(despite Metadata
being an array).
Possible Solution
If we were somehow allowed to append to this array of Metadatas
, then what I'm trying to do would be possible.
Am I missing something, or approaching this the wrong way? There is still a lot about Inversify that I haven't learned yet.
The root Request from Container getNamed/getAllNamed/getTagged/getAllTagged allows specifying a single tag metadata entry, whereas dependency requests have the tag metadata provided by the named decorator and the tagged decorator and multiple metadata can be created. The metadata entries in Target.metadata have to have unique keys so you can only supply one name but if you wanted multiple 'names' supply the names as an array for the metadata value.
constructor( @tagged(multiNameTag,['One','Two']) public namedDependeny: INamedDependency){}
const oneOfNamedConstraintFactory=function(names:string[]){
return (request:interfaces.Request)=>{
let matched=false;
const customTags=request.target.getCustomTags();
if(customTags){
const multiNamedTag=customTags.find(t=>t.key===multiNameTag);
if(multiNamedTag){
matched= (multiNamedTag.value as string[]).some(n=>names.some(nm=>{
return n===nm
}))
}
}
return matched;
}
}
container.bind(NamedDependency).toSelf().when(oneOfNamedConstraintFactory(['Zero','Two']))
Thanks for getting back to me! I know I can do @tagged(multiNameTag,['One','Two'])
, but what I really want is getMultiTagged
, which is what it seems you're writing in #1134. I'm excited for that to be done! Thank you :)
I also want getMultiTagged
, this is a great feature, thank you for your contribution. But since this is not merged, can you give me some advice on how to implement a workaround? I don't want to create a fork
@JSilversun It has been six months since I looked at the source code and I do not remember how it functions ! If I get a chance I will have a look but it might be a few days.
This is something I would really want to as well. Is the development of this still a possibility? I could take over your PR and try to make that work. @tonyhallett could you review that for me?
@tonyhallett I've took the liberty to work on this. It was a pretty simple solution after all. Do you mind reviewing it?
@luiz290788 I need to concentrate on another repo so please try one of the other maintainers. If it is still here when I have finished I will definitely have look.
Thanks @tonyhallett I'll do that!
This topic and the related PR's seem to have gone stale, but I found myself with a similar need to simplify the bind
constraints. I implemented a simplistic class that implements the Builder pattern, and can be used thus:
container.bind(Parser).toConstantValue(SpecialisedEventParser)
.when(Constraint.named('EventParser').and.injectedInto(MyController).build())
The purpose here is that I have an abstract Controller
which does some boiler plate (in this case input/output parsing) which is inherited by case-by-case controllers (this seems analagous to the OP's ViewController/ViewControllerComponent use case).
@injectable class Controller {
constructor(
@inject(Parser) @named('EventParser') eventParser: Parser,
) {
}
The existing API seems overly complex, with multiple methods on both the container bind
and container get
sides (whenTargetNamed, getNamed etc, as well as the standalone functions like namedConstraint
, taggedConstraint
, typeConstraint
etc).
It seems like this could be unified with a single when
that accepts a constraint, with the ability to use a builder for complex constraints. if the API accepted a builder, the caller could avoid the need to specify the build()
method on each call. The builder can have a fluent interface like exists with the rest of the API. I'm not arguing at the moment that the mix of existing calls should be deprecated, but in time perhaps, they would become less needed.
I can see cases for .or
logic and perhaps parenthesising (.paren(builder: Constrained)
), and that's where the builder gets more complex/sophisticated - right not I don't have a specific need for that so haven't considered the implementation.
I'm relatively new to Inversify so I can't claim to offer resource to spec/implement this, but it seemed like there might be some use for this approach.
FWIW, I'm actually using it to implement AWS Lambdas, and using Zod as my parser, which allows me to transform from different inputs (AWS IotCore, HTTP Endpoints) where the source event
type varies, but the embedded payload is common and therefore can be handled by the same controller after a little massaging.