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 field
    injectionFunc.call(this)

    // now I can use the users field
    users.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.

select all
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.

select all
const class MyService {

    ** By default, afIoc would choose this ctor because it has the most parameters
    new 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.

select all
const class MyService {
    @Inject private const Users users

    new make(OtherService other, |This| in) {

        // let afIoc inject users and any other fields
        in(this)

        // use the other service
        other.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.

select all
const class MyService {

    new make(|This| in) {
        ....
    }

    @PostInjection
    Void doStuff(OtherService otherService) {
        otherService.doSomting()
    }
}

Use this for any post injection configuration.

Have fun!



Discuss