Services are Ember objects that can be injected anywhere in your application. They are singletons like controllers, so they are particularly well suited to storing state that you depend on throughout your application; they are probably most commonly used to store session data but the possible applications are limitless. There are a few different ways to access a service from your other objects. Today we will talk about injecting them using the Ember.inject.service function.

Today we are going to use a service to provide us with current user information. In a real production application, you would get this information from a server following authentication, but we will stub out our current_user in the service.

Let's start by generating an app, a component and a service.

ember new fake-user
cd fake-user
ember g component user-profile-box
ember g service current-user

To start, all we want to do is add a little information about our user in our service and make that available to our user-info component. Let's open our service at app/services/current-user.js and add a userInfo property.

import Ember from 'ember';

export default Ember.Service.extend({
  userInfo: Ember.Object.create({
    id: 1,
    firstName: 'Brandon',
    lastName: 'Blaylock',
    email: 'brandon.blaylock@gmail.com',
    twitter: 'baroquon',
    github: 'baroquon',
  })
});

Now that we have a service in place, let's add our component to our application template.

In app/templates/application.hbs:

<h1>App with Current User Service</h1>
{{user-profile-box}}
{{outlet}}

Now we can start our app.

ember s

Let's add our service to our component. In app/components/user-profile-box.js we can inject our service by adding currentUser: Ember.inject.service(). For now let's just list all of our userInfo object's properties in our component's template.

We can do that by using Ember template helper each-in. In app/templates/component/user-profile-box.

<ul>
  {{#each-in currentUser.userInfo as |key value|}}
    <li>{{key}}: {{value}}</li>
  {{/each-in}}
</ul>

Now, if we look at our site, we will see all of our userInfo properties listed. This isn't really ideal as we probably don't want all this listed, certainly not listed as we have it here. Let's pause here and take a look at something interesting that is happening with our service. When we use Ember.inject.service() we are lazily loading our service into our component. To demonstrate this, we can remove all the content in our component's template that references our service. Now, let's add an init function to our service:

init(){
  console.log('The current-user service has been initialized.');
},

If we open up our browser's console, we will see that we do not get this log from our service. If however, we reference the service, by adding back the content to component's template, we will see that the init function in the service fires and our service is initialized. This could be significant if you are referencing your service's properties from your component's JavaScript. You must reference lazily loaded services using the get function. For example, in our component, we might want to create a computed property that returns a gravatar image of our current user. This computed property is taken from the Ember homepage. We will need our computed property to look like this:

gravatarUrl: Ember.computed('email', function() {
  const email = this.get('currentUser.userInfo.email');

  return `http://www.gravatar.com/avatar/${md5(email)}?s=200`;
})

So, we have to call this.get(...) to access our service's properties in the component, otherwise we might get undefined. If you think about the Ember store as a service, you might wonder, "Hey, why can I just call this.store.get(''), but for this I have to call, this.get('currentUser...')". It's the lazy injection. Tomorrow, we will talk about more eager loading. But, for now, let's get back to our example.

This gravatar example won't work just yet as we don't have access to the md5 function. On the Ember homepage they show us the component and have this md5 function there, but they don't tell us how to get it. Let's add it. It is actually pretty easy. We'll start off by using bower to install JavaScript-MD5 from blueimp. From your ember project root just run bower install blueimp-md5. Now, we will want to include that in our application, by referencing it in our ember-cli-build.js. This is essentially your Brocfile.js if you were using vanilla broccoli to build your assets - also vanilla broccoli sounds disgusting.

/*jshint node:true*/
/* global require, module */
var EmberApp = require('ember-cli/lib/broccoli/ember-app');

module.exports = function(defaults) {
  var app = new EmberApp(defaults, {});
  app.import('bower_components/blueimp-md5/js/md5.min.js');

  return app.toTree();
};

Now you can restart your ember server and you will be able to use md5 as a global. We could just add /* global md5 */ at the top of any file that we wanted to use the md5 function and jshint wouldn't complain and we could go about our merry way. But we can do a little better than that. Let's shim this md5 global and make it work like a module. We will do this by creating a JavaScript file in our vendor directory. We'll use define, we'll call our module md5 of course, it doesn't have any dependencies, and the callback will just return md5.

In vendor/md5-shim.js add:

/* globals md5 */

define('md5', [], function() {
  return {
    'default': md5
  };
});

Finally we can add this shim to our ember-cli-build.js file. Directly under app.import('bower_components/blueimp-md5/js/md5.min.js'); add app.import('vendor/md5-shim.js');. Now, in your component js, import md5 from 'md5';. And finally, in your component's template let's add our gravatar image and the content we actually want to have show up:

<img src={{gravatarUrl}}>
<br>
{{currentUser.userInfo.firstName}} {{currentUser.userInfo.lastName}}
<br>
{{currentUser.userInfo.email}}

Hey! stuff works. We have a service which we lazily injected. We installed a bower package and faked a module for a global. What a day. Tomorrow we will look at eager loading a service. Then we will discuss modifying a service's data by calling methods on the service. See you then.

Notes

As a bit of an aside, be careful about your use of services. With overuse, or misuse, they can quickly become tangled throughout your code and make your data story confusing, your code error prone, and your app difficult to debug. You should only use services if the state they contain is independent of your route. And remember of course, that a service is a singleton so throughout the entire life of your application you you will have only once instance of your service.

Resources

Tags

services, dependency injection, bower, broccoli, md5