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.

  1. Accept a parameter to specify which Daily Drip feed we want.
  2. If we already have the results in our feed and the topic is unchanged, return existing results to the component
  3. Otherwise fetch an rss feed from daily drip.
  4. Convert the feed to JSON
  5. 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.

Resources