0

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?

Jorge Garcia
  • 2,042
  • 23
  • 25
  • Someone didn't bother to read this: https://stackoverflow.com/help/privileges/vote-down – Jorge Garcia Feb 28 '18 at 18:51
  • 1
    Does this answer your question? https://stackoverflow.com/questions/45219953/create-a-new-instance-of-a-class-that-is-using-dependency-injection-in-aurelia – Nick Mar 01 '18 at 19:00
  • Nick, thanks for replying. I was actually looking for ways get a reference to the scoped Container, and sort of document its API for resolving new instances here. – Jorge Garcia Mar 07 '18 at 19:48

1 Answers1

4

To get the container in your view-model you generally have 3 options:

1. Inject the container

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.

2. Use the global container

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.

3. "Abuse" the router

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.

Once you have a reference to the container:

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.

Lifetime and scope

There are two lifetime registration types:

  • singleton gives the same instance for the lifetime of the container
  • transient gives a new instance each time you call the container

The 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.

Setting the registration type

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 :)

Fred Kleuver
  • 7,797
  • 2
  • 27
  • 38
  • Fred, I am not accomplishing anything in particular other than understanding how these APIs work and documenting them here. For the sake of completeness and to make it clear for others, would you please edit your answer to emphasize the registration and resolution APIs available through a Container instance (with sample code) so I can accept your answer? Most of it is described here: http://aurelia.io/docs/fundamentals/dependency-injection#how-aurelia-uses-containers – Jorge Garcia Mar 07 '18 at 17:23
  • Fair enough. I edited it with that goal in mind but I don't think this is the right place to go over the whole API though, so I scoped it (no pun intended) to your question of directly calling the container. If the docs are not clear enough about this then perhaps I should go and edit those.. – Fred Kleuver Mar 07 '18 at 18:54