In Aurelia (+ TypeScript)...
Is there a way to directly reference the container in context (ex. in a view model) and explicitly "request" new instances from it?
In Aurelia (+ TypeScript)...
Is there a way to directly reference the container in context (ex. in a view model) and explicitly "request" new instances from it?
To get the container in your view-model you generally have 3 options:
Decorate your viewmodel with @inject(Container)
or simply apply any decorator (which makes tsc emit type metadata) and make sure the type is specified in your constructor like so:
constructor(private container: Container) {}
This is the recommended way to get a container as it will give you the child container scoped to that particular view model. Meaning if you request things like Element
or Router
, you'll also get the ones scoped to that view model.
Things you register to that container are only resolvable through that container or its children - not its siblings or parents.
There is always one "root" container which you can access anywhere in your code via the Container.instance
static property.
This can be useful for some components that kind of live outside the normal aurelia lifecycle, or if you really need the root. You'll want to avoid this when you can though as it leads to spaghetti code.
I wouldn't necessarily recommend this but there's always a .container
property on every configured router. This is the scoped child router - the same one you'd get if you injected it in the constructor of your view model.
Call container.get(Foo)
, to get an instance of Foo
from only that container, or call container.getAll(Foo)
to get a list of all Foo
's from that container and all of its parents, up to the root.
For constructors it defaults to calling the constructor and recursively resolving its dependencies if it has any. Then it stores the instance as a singleton.
For anything that's not a constructor (except null
and undefined
) it defaults to storing the value and returning it whenever you call it with the same value again (not particularly useful but at least no error).
For null
or undefined
it will throw an error.
There are two lifetime registration types:
singleton
gives the same instance for the lifetime of the containertransient
gives a new instance each time you call the containerThe lifetime of a singleton
is further determined by the lifetime of the container it's registered to which, in the case of a typical child container, is the lifetime of the view model.
Other parts of the API surface are essentially just differently-scoped variants of either singleton
or transient
.
Many options here and I won't go into all of them here. The one that's relevant for you is the direct container API:
container.register...(key, fn)
After that, when you call container.get(key)
it will resolve the dependency according to the registration you just set it to. You can change this after the fact as well - will just overwrite the existing resolver.
singleton: container.registerSingleton(Foo)
instance (singleton but you provide the instance): container.registerInstance(Foo, new Foo(new Bar()))
transient: container.registerTransient(Foo)
custom function: container.registerHandler(Foo, (container, key, resolver) => new Foo(container.get(Bar))
(for a transient Foo
with a singleton Bar
)
There are other options but these are the ones most commonly used.
Last note about the key, fn
arguments: calling register(Foo)
is equivalent to calling register(Foo, Foo)
. You could also say register("foo", Foo)
if you don't want/don't have a reference to the class name from where you want to call it.
Being able to open the debugger and say document.body.aurelia.container.get("foo")
is something I personally find quite handy for debugging sometimes :)