So far we have built an addon with a single component that does very little. Yesterday we looked at testing our addon and linking it to existing projects. Today, we will use a service to provide our component with data. To start let's generate a service.
ember g service feed
Let's talk for a second about what we want our service to do.
- Accept a parameter to specify which Daily Drip feed we want.
- If we already have the results in our feed and the topic is unchanged, return existing results to the component
- Otherwise fetch an rss feed from daily drip.
- Convert the feed to JSON
-
return a results object to the component.
-
This should be an object with two properties:
- title - title of the rss feed
- feed - content of the rss feed
-
Ok, so I think that lays it out pretty nicely.
Before we get started, let's make one change to how our service is set up. Let's
use ES2015 Object destructuring to assign
Service
to a variable so that we
don't have to have tp pull everything from the Ember object. We are just saying
get the
Service
property from the Ember object and assign it as a variables
Service
. Then we can remove
Ember
before
Service
. If we are so inclined,
we can also get the
get
method from Ember rather than
this
and pass in the
context. The only functional difference here is that
Ember.get
(and
Ember.set
for that matter) doesn't care if you are dealing with a plain
JavaScript object or an Ember Object, whereas,
this.get
and
this.set
are
specific to Ember objects.
import Ember from 'ember';
const { get,
Service,
set } = Ember;
export default Service.extend({
});
Now, we need a method on our service that returns the expected results object, or sets it if it is empty. It should also set the results feed, if the topic that pass passed in has changed. For the sake of speeding things along, let's go ahead and create three failing tests, instead of doing them one by one. The first test, will test that if we have results in our feed and our topic matches, we don't call a setFeed function. The second test will test that if their are no feed results, we do call a setFeed function. Finally, we will test if we have results, but we pass in a different topic, we still call the setFeed function.
test('If we already have a results in our feed we return then without calling get setFeed Function', function(assert) {
const service = this.subject();
service.set('results', { title: 'Service Title', feed: [1, 2, 3]});
service.set('topic', 'ember');
service.set('setFeed', () => {
return { title: 'Wrong Title', feed: ['wrong', 'feed' ] };
});
let results = service.getFeed('ember');
assert.equal(results.title, 'Service Title');
assert.notEqual(results.feed.indexOf(1), -1);
assert.equal(results.feed.length, 3);
});
test('If we do not already have a results in our feed we call a setFeed Function which returns them', function(assert) {
const service = this.subject();
service.set('topic', 'ember');
service.set('setFeed', () => {
return { title: 'Right Feed', feed: ['right', 'feed' ] };
});
let results = service.getFeed('ember');
assert.equal(results.title, 'Right Feed');
assert.notEqual(results.feed.indexOf('right'), -1);
assert.equal(results.feed.length, 2);
});
test('Even if we already have a results in our feed, if we pass in a different feed we still call a setFeed Function which returns the new feed', function(assert) {
const service = this.subject();
service.set('results', { title: 'Service Title', feed: [1, 2, 3]});
service.set('topic', 'ember');
service.set('setFeed', () => {
return { title: 'Right Feed', feed: ['right', 'feed' ] };
});
let results = service.getFeed('elm');
assert.equal(results.title, 'Right Feed');
assert.notEqual(results.feed.indexOf('right'), -1);
assert.equal(results.feed.length, 2);
});
Since I just pasted these in, you can inspect them more closely on github, I'll link to them in the episode resources. These test are certainly a little more verbose than they have to be, but hopefully they are equally obvious with what they are doing. Making these test pass is really simple. Back in our service, we will just add a getFeed function with a simple conditional that satisfies our requirements and we will return the appropriate item. Finally, since we are returning the setFeed function, we will create an empty function there.
setFeed(){
},
getFeed(topic){
if(Ember.isPresent(get(this, 'results')) && topic === this.get('topic')){
return get(this, 'results');
} else {
return this.setFeed(topic);
}
}
Now, let's partially implement the
setFeed
function. We aren't going to
complete it, but we can get a good start. We know, based on what we have written
so far that it will take one parameter, the topic, and it will return the
results
property. We aren't going to get into the details today, but we will
start by learning one really helpful tool that we will need to make this
function work: Promises. Ember comes with RSVP bundled in as the tool for
creating promises. If you don't know much about JavaScript promises, there are a
lot of great resources online to get you up to speed. Essentially, a promise is
just an object that helps you deal with asynchronous code which has a
then
method. The promise exists in one of three states, "pending", "fulfilled", or
"rejected". To create a new promise in Ember world, we will first import RSVP
from 'rsvp'.
import RSVP from 'rsvp';
Then, you just instantiate your new promise. This is what your setFeed function will return. So, just type "return new RSVP.Promise". We will pass in resolve and reject. And finally, we can write whatever async code we need to write. For success we will "resolve" with the value that we want to return, or for failure, we will "reject" with an error object and a message.
setFeed(topic){
let feedPromise = new RSVP.Promise((resolve, reject) =>{
if (/*all is well*/) {
resolve(/* some resolvable item */);
} else {
reject(new Error(`Failed!!!!!!!!`));
}
});
return set(this, 'results', feedPromise);
}
The only thing left to do here is to finish implementing our setFeed function.
But, that will have to wait for tomorrow. That's it for today. We looked at
mapping out a plan for a service, test driving some of the service, Object
destructuring, flavors of
get
and
set
, and finally RSVP's implementation of
Promises. Moving forward we still need to fetch and populate our addon with the
rss data from daily drip, we will fill out our readme, and finally, we will
publish our addon.
Comments for Creating a Service and Making a Promise