Note: This article has since been merged into the IoC documentation.
It is recommended you look there for up to date IoC info.
We've seen how to create Ioc services, but how do you use them? How do you inject an IoC service into another service?
The simple answer is to use @Inject
- but as usual, there's more to it than that... So let's take a closer look.
In these examples we'll look at how to inject an imaginary service called Users
into another service called MyService
. We shall look at:
It-Block Injection
We'll start with the most popular and easiest method of injection; field injection via a it-block ctor (see This).
using afIoc const class MyService { @Inject private const Users users new make(|This| f) { f(this) } ... }
MyService
is a const
class, as every good service should be. This means all its fields, including Users
, also have to be const
. This means Users
HAS to be set in the ctor. If users
is set in the ctor it means it can be non-nullable.
You don't have to understand the above code, just cut'n'paste the ctor into your service! Then your service can be const, and all your injected services can be const and non-nullable too! Fantastic!
But if you want to understand, read on...
The |This|
it-block is a special function reserved (mainly) for ctors. It is a function into which you pass your class instance. When IoC sees a ctor with this special parameter, it passes in a function that sets all fields labelled with @Inject
. So to set your fields, just call the function!
A more verbose ctor example could be:
new make(|This| injectionFunc) {// right here, the users field is null// let afIoc set the users fieldinjectionFunc.call(this)// now I can use the users fieldusers.setIq("traci", 69) }
Note this is the ONLY way to have IoC set const
fields because ALL const
fields have to be set in the ctor. (Fantom rules.)
This is often referred to as the serialisation ctor
because this is how the Fantom serialisation mechanism sets const
fields when it inflates class instances.
Ctor Injection
If you want more control over how your services are set, consider ctor injection. When IoC instantiates your class, it doesn't just look for the it-block parameter - it looks at ALL the parameters and tries to find a matching service.
using afIoc const class MyService { private const Users users private const OtherService otherService new make(Users users, OtherService otherService) { this.users = users this.otherService = otherService } ... }
Note how the services are not annotated with @Inject
, IoC passed the services into the ctor and we set them ourselves.
This kind of injection is good for manual unit testing for you can easily inject mock services into your class.
But what if your service has multiple ctors?
By default, IoC will choose the ctor with the most parameters. This behaviour can be overridden by annotating a chosen ctor with @Inject
.
const class MyService {** By default, afIoc would choose this ctor because it has the most parametersnew make(Users users, OtherService otherService) { .... }** But by annotating with @Inject we force afIoc to use this ctor@Inject new make(|This| in) { .... } }
Field Injection
Field injection requires the least amount of work on your behalf, but is quite limiting:
using afIoc class MyService { @Inject private Users? users @Inject private OtherService? otherService ... }
Here we don't specify any ctor! Once constructed, IoC just sets our fields. Because the fields get set outside of the ctor, the fields have to be nullable
AND they can not be const.
Be aware that if a service not const then IoC is forced to create a new instance for each thread / web request.
Mixed Injection
If I only want use a service in the ctor, then there's no point creating a whole field for it; I may as well just inject it as a ctor parameter. But if I want IoC to set all my other fields I can still declare an it-block parameter. This is an example of mixed injection.
const class MyService { @Inject private const Users users new make(OtherService other, |This| in) {// let afIoc inject users and any other fieldsin(this)// use the other serviceother.doSomthing() } }
Note that the it-block parameter HAS to be the last parameter in the parameter list. Fantom demands it.
The service ctor's can be any scope you like! Public, protected, internal or private. They are public in these examples, purely for brevity.
Post Injection
Once IoC has instantiated your service, called the ctor, and performed any field injection, it then looks for any methods annotated with @PostInjection
- and calls it. Similar to ctor injection, @PostInjection
methods may take services as parameters.
const class MyService { new make(|This| in) { .... } @PostInjection Void doStuff(OtherService otherService) { otherService.doSomting() } }
Use this for any post injection configuration.
Have fun!