Ember addons are a great way to share reusable code between applications. At their most basic level, an addon is just an Ember specific NPM package.
Today we will be creating an Ember addon that will display a list of daily drip episodes for any topic that the consumer of the addon chooses. This should help expose you to the basics of creating an Ember addon and hopefully, you will be able to use your new knowledge to create a bunch of really useful addons.
Creating an addon is a lot like creating an new Ember application, but instead
of typing
ember new ${name}
we use
ember addon ${name}
for our generator.
So, let's start.
ember addon ember-daily-drip-feed
This will generate a new addon shell for us. If you cd into your new addon you will notice that it looks pretty much like an app but it has an addon directory. While we are in here, let's go ahead and run our tests.
ember t
As you can see, tests run just like any ember app. Also, just like any Ember
app, there is an app directory. As you build your addon you should keep in mind
that everything you put in your addon's
app
directory will be merged with the
app
directory of the consuming Ember application. However, unlike normal Ember
apps, most of your code in your addon will go in your
addon
directory not your
app
directory. You will then import any objects from your addon directory into
the app directory. This will become more obvious as we start building our addon
and as always, Ember CLI will help us a lot along the way.
Let's get started by generating our primary component.
ember g component daily-drip-feed
Now, we have something that looks noticeably different than a normal app. This generate command created a component in app/components and in addon/components and it put the component's template in addon/templates/components. So, let's run our tests again.
ember t
And they no longer pass. But, because Ember CLI is awesome we know exactly why
it doesn't pass and we can easily fix it. Because of the way the template and
layout work for the addon's component we need to include "ember-cli-htmlbars" in
our dependencies rather than our devDependencies. Just open the addon's
package.json
file and move that dependency. While we are in here, we can also
add a few relevant keywords so that when we publish our addon it can easily be
found. For our addon, I am going to add: "daily drip", "learning", and
"education".
Now, let's start making our component do what we want it to. We will worry about fetching data later, for now, let's just get it looking like we want. Let's open our component's integration test and add some assertions that will fail. Since we are going to use this as an inline component and we can get rid of the block level/yield test.
First, let's set up what we our feed might look like. I think we will return an
object that will have a title property and a feed property. The title will
contain a generic title for the whole feed, and the feed will contain an array
with the actual feed items. So, let's assert that we have an h3 that contains
the
results.title
value and an unordered list that has the same number of li
elements as the
results.feed
's length.
tests/integration/components/daily-drip-feed.js
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
moduleForComponent('daily-drip-feed', 'Integration | Component | daily drip feed', {
integration: true
});
test('it renders the title and a list with the appropriate number of items.', function(assert) {
let results = { title: 'title', feed: [1, 2, 3]};
this.set('results', results);
// Handle any actions with this.on('myAction', function(val) { ... });
this.render(hbs`{{daily-drip-feed results=results}}`);
assert.equal(this.$('[data-feed-header]').text().trim(), 'title');
assert.equal(this.$('[data-feed-list] > li').length, 3);
});
This test will fail, let's make it pass. In our template
(
addon/templates/components/daily-drip-feed.hbs
) lets add our header with the
data attribute we are using as a selector in our test and pass in our results
title attribute. And let's add a ul with our data attr and use an each helper to
iterate over the results' feed array. Finally add list items.
<h3 data-feed-header>
{{results.title}}
</h3>
<ul data-feed-list>
{{#each results.feed as |item|}}
<li>
{{item}}
</li>
{{/each}}
</ul>
Now, that works. Let's make one more change today. The feed wont be just a list of numbers or strings, it will be a list of objects each with a link and title property. Back in our test, we'll set it up for that.
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
moduleForComponent('daily-drip-feed', 'Integration | Component | daily drip feed', {
integration: true
});
test('it renders the title and a list with the appropriate number of items.', function(assert) {
let obj1 = {
title: '[Elm 021.1] Editing Users and a Week of Refactoring',
link: 'https://www.dailydrip.com/topics/elm/drips/editing-users-and-a-week-of-refactoring'
};
let obj2 = {
title: '[Elm 020.5] Elm Weekly Drip #20 and Exercise: Edit Users',
link: 'https://www.dailydrip.com/topics/elm/drips/elm-weekly-drip-20-and-exercise-edit-users'
};
let obj3 = {
title: '[Elm 020.4] Finishing a CRUD Resource with a Sortable Table in elm-mdl',
link: 'https://www.dailydrip.com/topics/elm/drips/finishing-a-crud-resource-with-a-sortable-table-in-elm-mdl'
};
let results = { title: 'title', feed: [obj1, obj2, obj3]};
this.set('results', results);
this.render(hbs`{{daily-drip-feed results=results}}`);
assert.equal(this.$('[data-feed-header]').text().trim(), 'title');
assert.equal(this.$('[data-feed-list] > li').length, 3);
assert.equal(this.$('[data-feed-list] > li:first-child a').text().trim(), obj1.title);
assert.equal(this.$('[data-feed-list] > li:first-child a').attr('href'), obj1.link);
});
Now, let's make that pass.
<h3 data-feed-header>
{{results.title}}
</h3>
<ul data-feed-list>
{{#each results.feed as |item|}}
<li>
<a href={{item.link}}>{{item.title}}</a>
</li>
{{/each}}
</ul>
So, that get's up where we need to be for now. We have an addon that gives any consuming application access to a component that displays a title, and a list. The list items have links and their own titles. That's it for today, tomorrow we will look a little more at addon tests and at the dummy app that is included with your addon. See you then.
Comments for Creating an Addon - with tests