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@smoothterminal.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
- EmberCLI loader.js
- JavaScript-MD5 from blueimp
- Ember homepage - The Gravatar Example.
Tags
services, dependency injection, bower, broccoli, md5
Comments for Services