Sunday, 22 March, 2015 UTC


Summary

Did you know Ember has the concept of services? and dependency injection? You did? Oh... Well, I feel silly for not knowing this sooner.
Until now I've been creating a "service" for my ajax calls like this:
// app/models/store.js
import Ember from 'ember';
import ajax from 'ic-ajax';

export default Ember.Object.extend({
  fetchPerson: function () {
      return ajax('http://api.randomuser.me/');
   }
});
... and then using it like this:
// app/routes/index.js
import Ember from 'ember';
import Store from 'ember-services/models/store';

export default Ember.Route.extend({
    model: function () {
     var store = Store.create();
     return store.fetchPerson();
    }
});
It felt okay to do it this way, but I didn't like how I had to call Store.create(), in larger applications I found I was repeating this a lot.
"It would be nice if I could use some IOC to get this into each route, controller and component!" I thought, not bothering to actually do any research.
It turns out you totally can do that.
Creating a Service
There's a Service class in Ember now, that extends the Ember.Object base class I was using above.
Lets create a service that does the same thing as above:
// app/services/store.js
import Ember from 'ember';
import ajax from 'ic-ajax';

export default Ember.Service.extend({
  fetchPerson: function () {
    return ajax('http://api.randomuser.me');
  }
});
This doesn't look too different from the Store object I created earlier, with the exception that it goes into it's own services folder.
Using the Service in a Controller
Now in our controllers we can get an instance of this service by using the fancy new injection API added in version 1.10.
// app/controllers/index.js
import Ember from 'ember';

export default Ember.Controller.extend({
    store: Ember.inject.service()
});
Using Ember.inject.service() we can tell Ember that we need our store service in this controller. Ember will then go off and lazy load our service at runtime.
In this example the name of service that is injected matches the name of the property that we are injecting the service into i.e We called the property "store", so Ember goes off and finds a service named "store". If it can't find one it will throw an error.
If you want to be a little more verbose about this, or if you want to name your property something different from the service you can tell Ember which service to grab like so:
//...
service: Ember.inject.service('store')
// ...
Finishing our controller we have something like this:
// app/controllers/index.js
import Ember from 'ember';

export default Ember.Controller.extend({
  store: Ember.inject.service(),
  person: function () {
    return this.get('store').fetchPerson();
  }.property()
});
That's pretty nice, but it doesn't solve our problem in larger applications where we have to request the service in each controller and route.
Luckily theres another way to inject our service using initializers
Dependency Injection with Initializers
// app/initializers/store.js
export default function initialize (container, app) {
    app.inject('route', 'store', 'service:store');
    app.inject('controller', 'store', 'service:store');
}

export default {
    name: 'store',
    initialize: initialize
}
In the initialize function we are given access to the app, and we can inject things into certain areas of it. In this case we are injecting our store service into every route and controller in the application. It's also possible to pick and choose specific routes or controllers like this:
app.inject('route:routeName', 'myService', 'service:myService');
Now if I wanted to access methods on this store I can do it like so:
// app/routes/index.js
import Ember from 'ember'

export default Ember.Route.extend({
        model: function () {
        return this.store.fetchPerson();
    }
});
I really like this way of doing things, it's very clean, and while it's a bit more code to get it all set up inially, you're not having to repeat the same code over and over in each route, controller, and component. For me, this is a pretty big deal in larger ember projects.
I have created a simple example of using services in this way which you can check out on GitHub.