javatemplatesdependency-injectionplayframeworktwirl

PlayFramework 2.5 Templates - Twirl Dependency Injection


since PlayFramework is moving like year from global state using dependency injection and none is probably able there to rewrite guides/tutorials I can not find answer to my question anywhere.

Since Play 2.5 you have to stay away from global state even in twirl templates. Then for example: you want to use WebJarAssets in your template then you have to use according this guide (http://www.webjars.org/documentation):

controller in Java:

public class Application extends Controller {

    @Inject WebJarAssets webJarAssets;

    public Result index() {
        return ok(index.render(webJarAssets));
    }

}

and template:

@(webJarAssets: WebJarAssets)
<!DOCTYPE html>
<html>
    <head>
        <link rel='stylesheet' href='@routes.WebJarAssets.at(webJarAssets.locate("css/bootstrap.min.css"))'>
    </head>
    <body>
    </body>
</html>

Ok that make sense and can be easy done. But your template have usually more then one parameter and now there is MeesageApi which you might want to use also in template and any other.. then in Java you will have to pass everything as parameter and your template will have thousand of parameters and will be hard to read and manage. Now if you move from older version like Play2.4 and less you need to edit parameters for every template you made probably and then also every controller and if your project is really big this is so much unnecessary work..

So there should be DI available also in templates. This issue should be already fixed here: Injectable templates

So I followed what was written there and added to plugins.sbt:

addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.2.0")

added to build.sbt:

TwirlKeys.constructorAnnotations += "@javax.inject.Inject()"

also I have resolver as following:

resolvers += Resolver.sonatypeRepo("snapshots")

Then I added DI to twirl template:

@import play.twirl.api.HtmlFormat
@import b3.vertical.fieldConstructor
@import views.html.menu.menu
@import controllers.WebJarAssets
@(title: String)(implicit headInsert: Html = HtmlFormat.empty, content: Html = HtmlFormat.empty)

@this(webJarAssets: WebJarAssets) 

Restarted play and I am still getting error:

play.sbt.PlayExceptions$CompilationException: Compilation error[not found: value webJarAssets]

What did I miss? Should I also inject something inside controller? Is there anywhere example of play app, which use DI everywhere?


Solution

  • The ordering of the constructor call @this() with the dependency injected values and the parameters for rendering the template is important. You should place @this() before the regular template parameters.

    @import play.twirl.api.HtmlFormat
    @import b3.vertical.fieldConstructor
    @import views.html.menu.menu
    @import controllers.WebJarAssets
    
    @this(webJarAssets: WebJarAssets)
    
    @(title: String)(implicit headInsert: Html = HtmlFormat.empty, content: Html = HtmlFormat.empty)