Learning to Love Ember’s Dependency Injection

Ember is an opinionated framework. Like other such frameworks, it takes a while to learn how the pieces are designed to fit together and how you can structure your code to be harmonious with it, rather than fight it.

What I’ve found is that often, it helps to think outside the boxes ember provides you: controllers, routes, components, etc. Ember’s dependency injection is a great tool for helping you with this.

The Basics

Objects are named in Ember’s dependency injection container are given a full name that consists of two parts: the type of object, and a name. For example, route:application would be the name for your ApplicationRoute. Or controller:question.edit for your QuestionEditController. You’re not limited to Ember’s predefined categories, however; you are free to make up your own type.

So let’s say that you wanted to register some objects that formatted various kinds of data:

MyApp.register("formatter:date", DateFormatter);
MyApp.register("formatter:currency", CurrencyFormatter);

Great. Now you have a couple ways that you can access these objects. Your first option is to ask the DI container. As it turns out, every object that is instantiated by the DI container will be automatically given a reference to the container that created it, by using `this.container`. Now, in your controller, you may do:

PostController = Ember.ObjectController.extend({
     formattedDate: function(){
          var date = this.get("date");
          return this.container.lookup("formatter:date").format(date);
     }.property("date");
});

We can make this better, though. Ember’s DI allows you to specify automatic injections. This means that objects we pull out of the container will have their dependencies filled in immediately. To specify this dependency, we use the inject method.

MyApp.inject("controller:post", "dateFormatter", "formatter:date");

PostController = Ember.ObjectController.extend({
     formattedDate: function(){
          var date = this.get(“date”);
          return this.dateFormatter.format(date);
     }.property(“date”);
});

What if you wanted to have the date formatter immediately available to all your app’s controllers? No problem. You can specify only the type of object you want to inject into:

MyApp.inject("controller", "dateFormatter", "formatter:date");

Object Lifecycle

By default, objects are singleton. The first time a particular object is requested out of the container, it will instantiate it (using .create()), and each subsequent request will get the same instance.

It’s possible for you to specify that you do not want your object to be a singleton. In this case, each time you lookup an object (or each time it is injected as a dependency in another), a new instance will be created. For example:

MyApp.register("car:gti", GTI, {singleton: false});

Wrapping Up

Sadly, I get the feeling that Ember dependency injection is not as widely appreciated as it should be. I’ve leveraged DI in my Ember projects to great success. I’ve used it to achieve polymorphism, to enable code reuse, and to enhance the testability of my code. Overall, I find it hugely valuable and critical to achieving a well-architected codebase.
 

Conversation
  • To learn more about Dependency Injection in Ember, checkout Matthew Beale’s talk on Containers & Dependency Injection https://www.youtube.com/watch?v=6FlWyOoo6hQ

  • Comments are closed.