I’ve been using Ember for about two years now. One of the things that initially impressed me is you don’t need a lot of boilerplate code. At a bare minimum you need an
Ember.Application.create() and an application template to get your app running. It won’t do anything, but it is a perfectly functioning application. The first Ember app I wrote is still up on the Internet here: http://raytiley-msse-static-scheduler.herokuapp.com.
If you dig into the source code you’ll see there are only
Controllers. Alas, things were simpler back then. The app I develop today has
Serializers, and I think we just passed fifty
If you are new to Ember then this might seem like a lot to manage, but actually it is quite the opposite. All these things have meaning. They all have their own folder in my app, and just by naming them correctly Ember CLI magically wires them all together into a working application. My favorite thing about Ember is how it can take away the pointless choices, and nothing is more pointless then deciding where to put a thing, or what to call it.
However, it is not all rainbows and unicorns. There have always been these pesky things in real apps that don’t fit neatly into one of Ember’s buckets. They usually need to be used from multiple parts of your app, think logging, analytics, sessions, authentication, etc. Up until Ember 1.10 you had two basic approaches for these shared objects.
This is a fine approach. Controller is one of those generic words like manager. When programming if you’re not in a creative mood just call it a Manager. Unless you happen to be writing an Ember app, then just call it a Controller. You can then get access to your plethora of controllers using
controllerFor. This might look like the following:
So beyond the obvious fact that controllers are going away in Ember 2.0 this approach has some drawbacks. First, you have two different APIs for getting the same thing depending on if you’re trying to get it from a controller or a route. Second, you’re limited to where you can access these objects. I may only need to use my flash manager in controllers and routes, but something like a logging service would be useful in other locations such as components.
Dependency injection sounds scary, but it’s not. It is just a mechanism to reduce coupling between different objects in your application. The example above using
controllerFor is one way Ember does dependency injection. If you have ever passed an object to another object’s constructor you’ve done constructor injection.
Tight coupling makes code harder to maintain. Imagine you are an aspiring
photographer, but every time you want to take a photo you buy a new
iPhone to take a picture. This is analogous to creating a
new LoggingService() every time you want to log something in your application.
Besides being quickly broke, you the
photographer are tightly coupled to
iPhone. If we wanted to change the type of camera you use to ‘DigitalSLR’ we would need to follow you around and make sure we find every place you might take a picture and switch
DigitalSLR. This is analogous to having to change every place we use
new LoggingService() in a big application.
Now imagine that at the start of every day some magical person put a camera on your bed side table and you used that camera all day. Having the camera provided to you makes it easy to change
DigitalSLR. We have injected the camera dependency, and now our code is easier to maintain.
In Ember that magical person that can change your
camera in an
initializer. Initializers are just bits of code that run before your application is booted and let you set things up. In our initializer we will
register our dependency and then
inject it into the types of objects where we want to use it. Coding our logging service this way would look like this:
There are lots of different knobs that can be dialed in here. For instance the default behavior is we get a singleton logger, meaning that all the logger properties will be the same across routes, controllers, and components. We could change this. For more details read the api docs. So what’s wrong with this approach? Nothing in particular, I actually quite like it. There are a few things to be aware of however.
Notice I didn’t need to declare a
logger property on my
logger property is injected onto all my routes whether I like it or not. I find it good practice to declare my injected properties with
logger: null). I spent a long time once debugging an injected property conflicting with another property. By declaring it explicitly I prevent myself from accidentally using that name for something else.
Another thing about this approach is it can be a bit tedious to limit your injections. Something like logging we want everywhere, but that’s not always the case. You can configure inject a bunch, but if you’re lazy it is easy just to give all the routes something that maybe only one or two of them need.
What’s great about Ember is how it embraces paving cow paths. Pretty much every non trivial Ember application is doing some combination of the above. In my own app I have lots of
needs (pun intended) and several initializers injecting services. These patterns are tried, trued, and we know where all the cow shit is. So lets smooth them over using some new APIs. The controller part of our first example becomes:
Granted this doesn’t do a whole lot for us, we only got to remove one line of code. Also if we need access to a controller from a route we still need to use
controllerFor. What I’m really excited about is
Ember.inject.service(). Our second example becomes:
Sweet! We got to remove a whole file, the initializer is gone. Notice also that we moved
services. By placing our services in the
services folder Ember CLI will find them for us automatically. One less pointless choice. (the choice of
utils was pointless. I just made that up in my own app.) I also like this better because I’m explicitly delcaring that I want the
logger service available on
PostsViewRoute. Other routes won’t have a
logger automatically. This also means that I don’t have to worry about my injected property conflicting.
Now there are some rules for what can be injected where, but there is no public API for customizing that yet. For the most part you can inject services where you would think, including
components. This isn’t only for your own services. Addons can expose services and you can inject them into your apps objects. For example Ember Data has an open PR to allow you to expose the
store as a service.
I’m a big fan of this API. It allowed me to delete a bunch of initializers in our app, and removed some silly choices from my day to day development. I think the Services API shows how Ember can constantly iterate on ideas without making me throw away all my code.