Understanding Components in Ember 2
This article was peer reviewed by Edwin Reynoso and Nilson Jacques. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!
Components are a vital part of an Ember application. They allow you to define your own, application-specific HTML tags and implement their behavior using JavaScript. As of Ember 2.x components will replace views and controllers (which have been deprecated) and are the recommended way to build an Ember application.
Ember’s implementation of components adheres as closely to the W3C’s Web Components specification as possible. Once Custom Elements become widely available in browsers, it should be easily to migrate Ember components to the W3C standard and have them usable by other frameworks.
If you’d like to find out more about why routable components are replacing controllers and views, then check out this short video by Ember core team members Yehuda Katz and Tom Dale.
The Tab Switcher Application
To get an in-depth understanding of Ember components, we will build a tab-switcher
widget. This will comprise a set of tabs with associated content. Clicking on a tab will display that tab’s content and hide that of the other tabs. Simple enough? Lets begin.
As ever, you can find the code for this tutorial on our GitHub repo, or on this Ember Twiddle, if you’d like to experiment with the code in your browser.
The Anatomy of an Ember Component
An Ember component consists of a Handlebars template file and an accompanying Ember class. The implementation of this class is required only if we need extra interactivity with the component. A component is usable in a similar manner to an ordinary HTML tag. When we build our tab switcher component, we will be able to use it like so:
{{tab-switcher}}{{/tab-switcher}}
The template files for Ember components live in the directory app/templates/components
. The class files live in app/components
. We name Ember components using all lowercase letters with words separated by hyphens. This naming is by convention so we avoid name clashes with future HTML web components.
Our main Ember component will be tab-switcher
. Notice I said main component because we will have several components. You can use components in conjunction with others. You can even have components nested inside another parent component. In the case of our tab-switcher
, we will have one or more tab-item
components like so:
{{#each tabItems as |tabItem| }}
{{tab-item item=tabItem
setSelectedTabItemAction="setSelectedTabItem" }}
{{/each}}
As you can see, components can also have attributes just like native HTML elements.
Create an Ember 2.x Project
To follow along with this tutoial, you’ll need to create an EMber 2.x project. Here’s how:
Ember is installed using npm. For a tutorial on npm, you can see here.
npm install -g ember-cli
At the time of writing this will pull in version 1.13
ember -v
=> version: 1.13.8
Next, create a new Ember app:
ember new tabswitcher
Navigate to that directory and edit the bower.json
file to include the latest version of the Ember, ember-data and ember-load-initializers:
{
"name": "hello-world",
"dependencies": {
"ember": "^2.1.0",
"ember-data": "^2.1.0",
"ember-load-initializers": "^ember-cli/ember-load-initializers#0.1.7",
...
}
}
Back in the terminal run:
bower install
Bower might prompt you for a version resolution for Ember. Select the 2.1 version from the list provided and prefix it with an exclamation mark to persist the resolution to bower.json
.
Next start Ember CLI’s development server:
ember server
Finally navigate to http://localhost:4200/ and check the version your browser’s console.
Creating the Tab Switcher Component
Let’s create a tab switcher component using Ember’s built in generator:
ember generate component tab-switcher
This will create three new files. One is a Handlebars file for our HTML (app/templates/components/tab-switcher.hbs
), the second is a JavaScript file for our component class (app/components/tab-switcher.js
), the final one is a test file (tests/integration/components/tab-switcher-test.js
). Testing the component is beyond the scope of this tutorial, but you can read more about that on the Ember site.
Now run ember server
to load up the server and navigate to http://localhost:4200/. You should see a welcome message titled “Welcome to Ember”. So why isn’t our component showing up? Well, we haven’t used it yet, so lets do so now.
Using the Component
Open the application template app/templates/application.hbs
. Add in the following after the h2
tag to use the component.
{{tab-switcher}}
In Ember, components are usable in two ways. The first way, called inline form, is to use them without any content inside. This is what we’ve done here. The second way is called block form and allows the component to be passed a Handlebars template that is rendered inside the component’s template wherever the {{yield}}
expression appears. We will be sticking with the inline form throughout this tutorial.
This still isn’t displaying any content on the screen, though. This is because, the component itself doesn’t have any content to show. We can change this by adding the following line to the component’s template file (app/templates/components/tab-switcher.hbs
):
<p>This is some content coming from our tab switcher component</p>
Now when the page reloads (which should happen automatically), you will see the above text displayed. Exciting times!
Create a Tab Item Component
Now that we have setup our main tab-switcher
component, let’s create some tab-item
components to nest inside it. We can create a new tab-item
component like so:
ember generate component tab-item
Now change the handlebars file for the new component (app/templates/components/tab-item.hbs
) to:
<span>Tab Item Title</span>
{{yield}}
Next, let’s nest three tab-items
inside our main tab-switcher
component. Change the tab-switcher
template file (app/templates/components/tab-switcher.hbs
) to:
<p>This is some content coming from our tab switcher component</p>
{{tab-item}}
{{tab-item}}
{{tab-item}}
{{yield}}
As mentioned above, the yield
helper will render any Handlebars template that is passed in to our component. However, this is only useful if we use the tab-switcher
in its block form. Since we are not, we can delete the yield
helper altogether.
Now when we view the browser we will see three tab-item components
, all saying “Tab Items Title”. Our component is rather static right now, so lets add in some dynamic data.
Adding Dynamic Data
When an Ember application starts, the router is responsible for displaying templates, loading data, and otherwise setting up application state. It does so by matching the current URL to the routes that you’ve defined. Let’s create a route for our application:
ember generate route application
Answer “no” to the command line question to avoid overwriting the existing application.hbs
file. This will also generate a file app/routes/application.js
. Open this up and add a model property:
export default Ember.Route.extend({
model: function(){
});
});
A model is an object that represents the underlying data that your application presents to the user. Anything that the user expects to see should be represented by a model. In this case we will add the contents of our tabs to our model. To do this alter the file like so:
import Ember from 'ember';
export default Ember.Route.extend({
model: function(){
var tabItems = [
{
title: 'Tab 1',
content: 'Some exciting content for the tab 1'
},
{
title: 'Tab 2',
content: 'Some awesome content for the tab 2'
},
{
title: 'Tab 3',
content: 'Some stupendous content for the tab 3'
}
];
return tabItems;
}
});
Then change the tab-switcher
template file (app/templates/components/tab-switcher.hbs
) to:
{{#each tabItems as |tabItem| }}
{{tab-item item=tabItem }}
{{/each}}
Next, change the content of the tab-item
template file (app/templates/components/tab-item.hbs
) to:
<span>{{item.title}}</span>
{{yield}}
Finally change the tab-switcher
usage in the application.hbs
file to:
{{tab-switcher tabItems=model}}
This demonstrates how to pass properties to a component. We have made the item
property accessible to the tab-item
component template. After a page refresh, you should now see the tab item titles reflecting data from the models.
Adding Interactions Using Actions
Now let’s make sure that when a user clicks on a tab-item
title, we display the content for that tab-item
. Change the tab-switcher
template file (app/templates/components/tab-switcher.hbs
) to:
{{#each tabItems as |tabItem| }}
{{tab-item item=tabItem setSelectedTabItemAction="setSelectedTabItem" }}
{{/each}}
<div class="item-content">
{{selectedTabItem.content}}
</div>
This change assumes that we have a tabItem
property on the tab-switcher
component. This property represents the currently selected tab-item
. We don’t currently have any such property so lets deal with that.
Inside a regular template, an action bubbles up to a controller. Inside a component template, the action bubbles up to the class of the component. It does not bubble any further up the hierarchy.
We need a way to send click actions to the tab-switcher
component. This should happen after clicking any of its child tab-item
components. Remember I said that actions get sent to the class of the component and not further up the hierarchy.
So it seems impossible that any actions coming from child components will reach the parent. Do not worry because this is just the default behavior of components and there is a workaround to circumvent it.
The simple workaround is to add an action to the tab-switcher
template (app/templates/components/tab-switcher.hbs
) like so:
{{#each tabItems as |tabItem| }}
<div {{action "setSelectedTabItem" tabItem}} >
{{tab-item item=tabItem setSelectedTabItemAction="setSelectedTabItem" }}
</div>
{{/each}}
<div class="item-content">
{{selectedTabItem.content}}
</div>
And to change the tab-switcher
class file (app/components/tab-switcher.js
) to look like
export default Ember.Component.extend({
actions: {
setSelectedTabItem: function(tabItem){
this.set('selectedTabItem', tabItem);
}
}
});
At this point if you view our app in the browser, it will work as expected.
However, this workaround does not address the fact an action only bubbles up to the class of the component, so let’s do it in a way that does. Keep the changes in app/components/tab-switcher.js
, but revert app/templates/components/tab-switcher.hbs
back to its previous state:
<div class="item-content">
{{selectedTabItem.content}}
</div>
{{#each tabItems as |tabItem| }}
{{tab-item item=tabItem setSelectedTabItemAction="setSelectedTabItem" }}
{{/each}}
Now let’s change the tab-item
template to:
<span {{action "clicked" item }}>{{item.title}}</span>
{{yield}}
And the tab-item
class file to:
export default Ember.Component.extend({
actions:{
clicked: function(tabItem){
this.sendAction("setSelectedTabItemAction", tabItem);
}
}
});
Here, you can see that we have added an action handler to deal with clicks on the tab-item
title. This sends an action from the tab-item
component to its parent, the tab-switcher
component. The action bubbles up the hierarchy along with a parameter, namely the tabItem
which we clicked on. This is so that it can be set as the current tab-item
on the parent component.
Notice that we are using the property setSelectedTabItemAction
as the action to send. This isn’t the actual action name that gets sent but the value contained in the property — in this case setSelectedTabItem
, which is the handler on the parent component.
Conclusion
And that brings us to the end of this introduction to Ember components. I hope you enjoyed it. The productivity benefits of using reusable components throughout your Ember projects cannot be understated (and indeed throughout your projects in general). Why not give it a try? The source code for this tutorial is available on GitHub.
Are you using components in Ember already? What have been your experiences so far? I’d love to hear from you in the comments.