4
votes

With the new Version 2.4/2.5 of the Play Framework they moved further towards injecting everything and removing the servers state. play.Play.application() is now deprecated. However, I need the application in my template (e.g. to get all supported languages displayed on all pages with play.i18n.Lang.availables(play.Play.application())).

I'm aware I could:

  • Pass play.Application explicitly to all of my templates.
  • Add an implicit parameter to my template like @()(implicit app: play.Application). However, in my Java-Project it's not really implicit, I have to pass it every time I render the template.
  • Create a Scala object providing the application implicitly. However, this also needs the deprecated play.api.Play.current.

How can I inject play.Application in my templates?

---- Update: ----

What I've tried so far, I created the following setup:

index.scala.html:

@(title: String)
@template(title) { //here is the play.Application obviously missing, however I don't want to pass it in every template - even worse in every Controller <-- this is the core of my question
    Welcome to my page!
}

template.scala.html:

@(title: String)(content: Html)(implicit app: play.Application)
<html>
    <head>
        <title>@title</title>
    </head>
    <body>
        <p>Is live? @app.isProd</p>
        @content
    </body>
</html>

Controller function:

public Result index() {
    return ok(views.html.index.render("home"));
}
4
what do you exactly need to do with play.Application?Supun Wijerathne
I would like to list all available Langs like new play.api.i18n.DefaultLangs(play.Play.application().configuration()).availables() to show a language-switcher.Itchy
I have put the answer as your requirement. If you get into any problem with that, do let me know. :)Supun Wijerathne
Unfortunately, no. I'm still looking for a way to inject something to my template. All answers so far would require me to pass it to all of my templates. The play.Application is only an example, I also don't know how to inject anything else like play.Configuration.Itchy
so basically you are struggling to inject anything right??Supun Wijerathne

4 Answers

1
votes

I just had to look into this for Play framework 2.6.x. It is possible to inject an object into a template according to the documentation: https://www.playframework.com/documentation/2.6.x/ScalaTemplatesDependencyInjection.

I implemented a simple sample (a bit contrived) and I used scala:

test.scala.html:

@this(configuration: play.api.Configuration)
@(key: String)
config = @configuration.get[Seq[String]](key).mkString(", ")

HomeController.scala

package controllers

import javax.inject._

import play.api._
import play.api.i18n._
import play.api.mvc._

/**
 * This controller creates an `Action` to handle HTTP requests to the
 * application's home page.
 */
@Singleton
class HomeController @Inject()(cc: ControllerComponents, testView: views.html.test) (implicit configuration: Configuration) extends AbstractController(cc) with I18nSupport{

  def test() = Action  { implicit request =>
    Ok(testView("play.i18n.langs"))
  }
}

routes:

GET        /test                controllers.HomeController.test()

The configuration object gets injected into views.html.test template and the view itself is injected into the controller. Note the @this(configuration: play.api.Configuration) statement in the template. Play will generate a class behind the template with a constructor that is injected the Configuration object.

Please note that the injected configuration into the controller doesn't have any role in this particular code. I experimented with other permutations before I found this solution ... Let's say that if you have an inner template used by an outer template called from the controller, and the inner template needs the configuration object you need to feed the configuration from the controller top down, and add an implicit configuration: play.api.Configuration parameter in all the templates in the hierarchy path right to the template that needs it, something like this: @(message: String)(implicit messagesProvider: MessagesProvider, configuration: play.api.Configuration) . Then the controller injected configuration is fed to the top template all the way to the template that needs it.

2
votes

You can get an application with Play.current() or inject an application in controller like this is explained in this question. The template should get the argument of type play.Application.

It should be something like this.

The template, let's say is injectappexample.scala.html:

@(app: play.Application)
.... use the app object 

The controller:

public class SomeController extends Controller {
    Provider<Application> applicationProvider;

    @Inject 
    public SomeController(Provider<Application> applicationProvider) {
        this.applicationProvider = applicationProvider;
    }

    public Result injectAppExample() {
        return ok(injectappexample.render(applicationProvider.get());
    }
}

It worth to reconsider sending the application object to the template. If you should send a particular configuration property value, inject Configuration in the controller, get the value from configuration object and send it to the template. In this case injecting of application is not needed at all.

The template:

@(value: String)
.... use the value 

The controller:

public class SomeController extends Controller {
    Configuration configuration;

    @Inject 
    public SomeController(Configuration configuration) {
        this.configuration = configuration;
    }

    public Result injectAppExample() {
        return ok(injectappexample.render(configuration.getString("SOME_PROPERTY"));
    }
}
0
votes

It is generally discouraged to inject the Application itself, because that makes your code very cumbersome to test. Instead, think about what you actually need and inject that directly.

If you have a number of things you need in pretty much every template, my suggestion would be to create some sort of a context class that you can inject in your controller and then pass it on to the template.

0
votes

First of all, though you are asking how to inject application what you really need is configuration.

You can do something like this in play 2.5. I'll show constructor injection you may use field injection as your requirement.

import com.google.inject.Inject;
import play.Configuration;

public class MyClass{
    private Configuration conf;

    @Inject
    public MyClass(Configuration conf){
        this.conf = conf;
    }

}

Now you have your configuration class. Then specifically for your requirement posted in your comment, you can do this.

 List<Object> langList = conf.getList("play.i18n.langs");  
 //conf.getList("") returns an object array. 
 //You can cast to Strings (assuming you are using Java8).

 List<String> languages = new ArrayList<>(langList.size());
 for (Object object : langList) {
    languages.add(Objects.toString(object, null));
 }

Now you can have your languages list in languages.