Understanding Ember.js Components

07 December, 2015
post-banner

Web developers must work within the limits of the available HTML elements to build the foundations of their web projects. Web components are a new web standard that allow developers to extend the current HTML specification to create custom and reusable elements that better serve their project's requirements.

The Ember.js framework closely follows the W3C's specification in their implementation of components, which is important as web components are expected to be a big part of the future of the web.

If you're unfamiliar with what exactly web components are, you can check out my previous post on 'An Introduction to Web Components'.

Web Components in Ember.js

Ember components consist of two parts: some JavaScript and a HTMLBars template. The JavaScript component file defines the behaviour and properties of the component. The behaviours of the component are typically defined using actions. The HTMLBars file defines the markup for the component's UI. By default the component will be rendered into a 'div' tag element, but a different element can be defined if required. Another great thing about templates in Ember is that other components can be called inside of a component's template.

To call a component in an Ember app, you must use {{curly-brace-syntax}}. By design, components are completely isolated which means that they are not directly effected by any surrounding CSS or JavaScript, although the component's input can be defined by setting attributes.

Generating a Component with Ember-CLI

In this article I am going to demonstrate a basic Ember component for displaying 'Pokemon profiles'. The component will render a simple component displaying a Pokemon's image with some relevant data about that specific Pokemon.

Ember-CLI is the Ember command line tool, it can be used to generate Ember projects with a 'strong conventional project structure'. We can also use it to generate new Ember components inside the project.

You'll need to install Ember-CLI globally on your machine.

$ npm install -g ember-cli

You can use the CLI to generate new projects, which will install all the required dependencies and then run 'ember server' to run the ember application locally.

$ ember new ember-components
$ cd ember-components
$ ember server

If your app was created successfully and you head over to localhost:4200 you should be presented with a heading that says 'Welcome to Ember'.

Once the Ember app has been generated and successfully run, a new component can be generated using the following command.

$ ember g component pokemon-profile
version: 1.13.13  
installing component  
  create app/components/pokemon-profile.js
  create app/templates/components/pokemon-profile.hbs
installing component-test  
  create tests/integration/components/pokemon-profile-test.js

This will create the required .js and .hbs files needed to define the component, as well as an Ember integration test (which I'll cover later in another article).

The components name needs to include a hyphen. This is an Ember convention, but it is an important one as it'll ensure there are no naming collisions with future HTML elements. Again, this is Ember trying to keep as close to the web component spec as possible.

The Component Template

To speed things up I am going to include the latest version of the Bootstrap CSS library to the projects app/index.html file. This will create some basic styling for the demo.

// app/index.html
...
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">  
...

If we simply wanted to display some static information to the page we could just write a small block of HTML without having to create a component. If we wanted to reuse the same code block, but display different information, we could use a component and set some expressions to display the correct data that has been passed into it.

In the app/templates/components/pokemon-profile.hbs file we can create some basic markup to display the profile of a Pokemon when the component is called.

<div class="col-lg-4 col-sm-6 col-xs-12 text-center">  
  <h2>{{name}}</h2>
  <h4>{{type}} type - #{{number}}</h4>
  <p>{{profile}}</p>
  <img src="{{image}}" class="img-rounded img-responsive" alt=poke-image width="500px">
</div>  

The {{name}}, {{type}}, {{number}}, {{profile}} and {{image}} are Handlebars expressions which will be used to receive and display the data to display when the component is called.

Passing Data to the Component

To allow the component to display the data properly, we need to pass it in. There is a longstanding debate in the Ember community over whether or not components should load data. For the purposes of this article, the data will be passed in manually for each of the properties placeholders.

In the app/templates/application.hbs file we can call the component as many times as desired and pass in relevant data for each attribute. Here we are going to add three pokemon-profile’s.

...
{{pokemon-profile
      image="http://cdn.bulbagarden.net/upload/thumb/2/21/001Bulbasaur.png/500px-001Bulbasaur.png"
      name="Bulbasaur"
      type="Grass"
      number="001"
      profile="Bulbasaur (Japanese: フシギダネ Fushigidane) is a dual-type Grass/Poison Pokémon. It evolves into Ivysaur starting at level 16, which evolves into Venusaur starting at level 32."
}}
{{pokemon-profile
      image="http://cdn.bulbagarden.net/upload/thumb/7/73/004Charmander.png/500px-004Charmander.png"
      name="Charmander"
      type="Fire"
      number="004"
      profile="Charmander (Japanese: ヒトカゲ Hitokage) is a Fire-type Pokémon. It evolves into Charmeleon starting at level 16, which evolves into Charizard starting at level 36."
}}
{{pokemon-profile
      image="http://cdn.bulbagarden.net/upload/thumb/3/39/007Squirtle.png/500px-007Squirtle.png"
      name="Squirtle"
      type="Water"
      number="007"
      profile="Squirtle (Japanese: ゼニガメ Zenigame) is a Water-type Pokémon. It evolves into Wartortle starting at level 16, which evolves into Blastoise starting at level 36."
}}
...

I have taken the data from the Bulbapedia Pokemon database. Now if you view the app in the browser again at localhost:4200 in the browser, you will see a very simple page displaying the components with the data we passed in.

pokemon

Adding Interactivity with Actions

Ember components can also use {{action}} helpers. In this example we can create a simple button that, when clicked, will either show or hide the Pokemon’s data.

…
<div class="col-lg-4 col-sm-6 col-xs-12 text-center">  
  {{#if isShowing }}
    <h2>{{name}}</h2>
    <h4>{{type}} type - #{{number}}</h4>
    <p>{{profile}}</p>
    <button {{action 'profileHide'}} class="btn btn-primary">Hide profile</button>
  {{else}}
    <img src="{{image}}" class="img-rounded img-responsive" alt=poke-image width="500px">
    <button {{action 'profileShow'}} class="btn btn-primary">Show profile</button>
  {{/if}}
</div>  
…

The updated component template contains some new HTMLBars expressions that determines whether the profile is showing or not, and whether to display the ‘show profile’ button or ‘hide profile’ button. To define the behaviour of these new expressions, we need to edit the JavaScript file for the component.

import Ember from 'ember';

export default Ember.Component.extend({  
  isShowing: false,
  actions: {
     profileShow() {
       this.set('isShowing', true);
     },

     profileHide() {
       this.set('isShowing', false);
     },
   },
});

By default, the isShowing variable is set to false, which means that the image will be displayed above the button to ‘show profile’. When the ‘show profile’ button is clicked, the profileShow() function is called which sets the isShowing variable to true and will display the Pokemons profile instead.

toggle-data

Customising Components with JavaScript

Ember components can also be customised using their corresponding JavaScript file. Here you can define the tag that the element will compile to, what class names it should have and bind data atributes to the component as well.

A good example of this is a tweet-button component, where you would want to use an anchor tag element and set the class name to ‘twitter-share-button’. Let’s generate a simple tweet-button component using Ember-CLI.

$ ember g component tweet-button

The Twitter documentation says that to create a tweet button some JavaScript is needed to be added to the projects index.html file.

…
<script>  
    window.twttr=(function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],t=window.twttr||{};if(d.getElementById(id))return;js=d.createElement(s);js.id=id;js.src="https://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);t._e=[];t.ready=function(f){t._e.push(f);};return t;}(document,"script","twitter-wjs"));  
</script>  
…

If you then open the JavaScript file that was generated for the component at app/components/tweet-button.js you are able to set the customisations for the component.

import Ember from 'ember';

export default Ember.Component.extend({  
  tagName: 'a',
  classNames: 'twitter-share-button',
  attributeBindings: ['data-url’, 'data-text’, 'data-size’, 'data-hashtags'],
});

Here we have simply set the HTML tag to an anchor a, the class name to ‘twitter-share-button’ and set attribute bindings for the url, text, size and hashtags of the tweet we want to send out when the button is clicked.

Now, back in the app/templates/application.hbs file we can call the tweet-button component with the required attributes.

…
{{tweet-button
    data-url='http://www.danielgynn.com/understanding-ember-components'
    data-text='understanding ember components'
    data-size='large'
    data-hashtags='emberjs'
}}
…

twitter-button

Conclusion

I really feel that web components are going to have a strong presence on the web in the near future, and it's good to see JavaScript frameworks adopting them. Ember is closely following the W3C conventions for web components and there is some speculation that they are soon to be replacing 'controllers' in Ember.

Hopefully this basic introduction was helpful in gaining an understanding of how web components work in an Ember app.

I am planning on writing more on Ember routing and Ember Data in the near future, so look out for those. If you have any suggestions, questions or general comments please leave a comment on this post or send me a tweet.

comments powered by Disqus