Ember Components: A Deep Dive

Ember.js is a JavaScript MVC framework that allows developers to create ambitious web applications. Although pure MVC allows a developer to separate concerns, it does not provide you with all the tools and your application will need other constructs. Today, I'm going to talk about one of those constructs. Ember components are essentially sandboxed re-usable chunks of UI. If you are not familiar with Ember, please check out Getting Started With Ember.js or the Let's Learn Ember Course. In this tutorial, we will cover the Web Components specification, learn how to write a component in Ember, talk about composition, explain the difference between an Ember view and an Ember component, and practice integrating plugins with Ember components.

A Word About Web Components

Ember components are based on of the W3C Web Components specification. The specification is comprised of four smaller specifications; templates, decorators, shadow DOM, and custom elements. Of these four concepts only three of them have harden specifications, decorators being the exception. By having the specifications in place, framework developers have been able to polyfill these new APIs prior to them being implemented by browser vendors.

There are several important concepts to grasp when talking about components:

Components know nothing about the outside world unless explicitly passed in

Components should have a well defined interface to the outside world

Components cannot manipulate any JavaScript outside of the component

Components can broadcast events

Custom elements must be namespaced with a hyphen

Outside JavaScript cannot manipulate components

Web Components provide true encapsulation for UI widgets. Below is a diagram of how a component works at the most basic level.

While Ember has successfully polyfilled a lot of a specification, frameworks like AngularJS, Dart, Polymer, and Xtags have similar solutions. The only caveat here is that Ember and Angular currently don't scope styles to the component. Over time, these polyfill solutions will fade away, and frameworks will adopt the browser vendor's implementation. This is a fundamentally different approach to development, as we can take advantage of future specifications without tying ourselves to experimental features in browsers.

The Most Basic Ember Component

With our knowledge of Web Components, let's implement the very basic my-name component from above, but in Ember. Let's begin by downloading the Ember Starter Kit from the Ember website. At the time of this tutorial the version of Ember is 1.3.0. Once you have it downloaded open up the files in your favorite editor, delete all of the templates in index.html (denoted with data-template-name) and everything in app.js.

The first thing we are going to want to do is create our component template. For the sake of this tutorial we are going to use inline templates. You do this by writing the following in your index.html file. We also need to create a new Ember application in our JavaScript.

You'll notice that the data-template-name has a path name instead of just a plain string. The reason why we prefix our component name with "components/" is to tell Ember we are dealing with a component template and not a regular application template. You'll also notice that the component name has the hyphen in it. This is the namespacing that I had mentioned in the Web Components specification. Namespacing is done so that we do not have name collisions with existing tags.

If we open the browser, we shouldn't see anything different. The reason for this that we have yet to place anything in our my-name template. Let's take care of that.

Now in the browser you should see something like the image above. We still aren't finished as you can see we actually aren't printing out a name. As I mentioned in the first section, components should expose a well defined interface to the outside world. In this case, we are concerned with the name. So let's pass in the name by placing a name attribute on the my-name component.

When you refresh the page you should see "Hi, my name is Chad". All of this with writing one line of JavaScript. Now that we have a feel for writing a basic component, let's talk about the difference between Ember components and Ember views.

Ember Components vs. Ember Views

Ember is an MVC, so some may be thinking, "Why not just use a view for this?" This is a legitimate question. Components actually are a subclass of Ember.View, the biggest difference here is that views are generally found in the context of a controller. Take the example below.

Views normally sit behind a template and turn raw input (click, mouseEnter, mouseMove, etc.) into a semantic action (openMenu, editName, hideModal, etc.) in a controller or route. Another thing to point out is that templates need a context as well. So what ends up happening is that Ember infers the context through naming conventions and the URL. See the diagram below.

As you can see, there is a level of hierarchy based on the URL and each level of that hierarchy has its own context which is derived through naming conventions.

Ember components do not have a context, they only know about the interface that they define. This allows a component to be rendered into any context, making it decoupled and reusable. If the component exposes an interface, it's the job of the context to fulfill that interface. In other words, if you want the component to render properly you must supply it with data that it's expecting. It's important to note that these passed in values can be both strings or bound properties.

When bound properties are manipulated inside of a component those changes are still propagated wherever they are referenced in your application. This makes components extremely powerful. Now that we have a good understanding of how components are different from views, let's look at a more complex example that illustrates how a developer can compose multiple components.

Composition of Components

One really nice thing about Ember is that it's built on concepts of UI hierarchy and this is very apparent with composition of components. Below is an example of what we are going to make. It's a simple group chat UI. Obviously, I'm not going to write a whole chat service to power the UI, but we can see how we can break the UI down into re-usable and compose able components.

Let's first look how we are going to break up the UI into smaller and more digestible parts. Anything that we can draw a box around is a component, with the exception of the text and button inputs at the bottom of the UI. Our goal is to be able to only configure the component at the outer layer, everything else should just work.

Let's start by creating a new html file called chat.html and setting up all of the dependencies for Ember. Next create all of templates.

You will see that components can be nested inside of other components. This makes components just like legos that we can assemble any way we want. We just need to write to the component's interface.

If we now go look in the browser we shouldn't see much because we don't have any data flowing into the component. You will also notice that even though there is no data, the components do not throw an error. The only thing that actually gets rendered here is the input area and the send button. This is because they aren't dependent on what is passed in.

Taking a little bit closer look at the templates you'll notice that we assigned a couple things on the group-chat component.

In this case, we are passing the model from the context of the IndexRoute as "messages" and we have set the string of sendMessage as the action on the component. The action will be used to broadcast out when the user wants to send a new message. We will cover this later in the tutorial. The other thing that you will notice is that we are setting up strict interfaces to the nested components all of which are using the data passed in from the group-chat interface.

As mentioned before you can pass strings or bound properties into components. Rule of thumb being, use quotes when passing a string, don't use quotes when passing a bound property. Now that we have our templates in place, let's throw some mock data at it.

If we go look at this in the browser now, we should see a bit of progress. But there are still some work to be done, mainly getting the images to show up, formatting the date, and being able to send a new message. Let's take care of that.

With our user-avatar component, we want to use a service called Avatars.io to fetch a user's Twitter avatar based on their Twitter user name. Let's look at how the user-image component is used in the template.

It's a pretty simple component but you will notice that we have a bound property called avatarUrl. We are going to need to create this property within our JavaScript for this component. Another thing you will note is that we are specifying the service we want to fetch the avatar from. Avatars.io allows you fetch social avatars from Twitter, Facebook, and Instagram. We can make this component extremely flexible. Let's write the component.

A couple points to note here. One is the on() declarative event handler syntax. This was introduced in Ember prior to the 1.0 release. It does exactly what you think it does, when the time-stamp component is inserted into the DOM, scheduleStartTime is called. When the element is about to be destroyed and cleaned up the killTimer method will be called. The rest of component just tells the time to update every minute.

The other thing you will notice is that there are several calls to Ember.run. In Ember there is a queueing system, normally referred to as the run loop, that gets flushed when data is changed. This is done to basically coalesce changes and make the change once. In our example we are going to use Ember.run.later to run the startTimer method every minute. We will also use Ember.run.cancel to dequeue the timer. This is essentially Ember's own start and stop interval methods. They are needed to keep the queuing system in sync. For more on the run loop I suggest reading Alex Matchneer's article "Everything You Never Wanted to Know About the Ember Run Loop".

The next thing we need to do is setup the action so that when the user hits submit, a new message will be created. Our component shouldn't care how the data is created it should just broadcast out that the user has tried to send a message. Our IndexRoute will be responsible for taking this action and turning into something meaningful.

Since the group-chat component owns the input and send button we need to react to the user clicking send at this level of abstraction. When the user clicks the submit button it is going to execute the submit action in our component implementation. Within the submit action handler we are going to get the value of message, which is set by by the text input. We will then send the action along with the message. Finally, we will reset the message to a blank string.

The other odd thing you see here is the Ember.run.schedule method being called. Once again this is Ember's run loop in action. You will notice that schedule takes a string as it's first argument, in this case "afterRender". Ember actually has several different queues that it manages, render being one of them. So in our case we are saying when the sending of the message is done making any manipulations and after the render queue has been flushed, call our callback. This will scroll our ul to the bottom so the user can see the new message after any manipulations. For more on the run loop, I suggest reading Alex Matchneer's article "Everything You Never Wanted to Know About the Ember Run Loop".

If we go over to the browser and we click the send button, we get a really nice error from Ember saying "Uncaught Error: Nothing handled the event 'sendMessage'. This is what we expect because we haven't told our application on how to reaction to these types of events. Let's fix that.

Now if we go back to the browser type something into the message input and hit send, we should see the message in the console. So at this point our component is loosely coupled and talking to the rest our application. Let's do something more interesting with this. First let's create a new Ember.Object to work as a model for a new message.

So when the sendMessage action occurs we are going to want to populate the text and timeStamp field of our Message model, create a new instance of it, and then push that instance into the existing collection of messages.

When we go back to the browser, we should now be able to create new messages.

We now have several different re-usable chunks of UI that we can place anywhere. For instance, if you needed to use an avatar somewhere else in your Ember application, we can reuse the user-avatar component.

Wrapping jQuery Plugins

At this point, you're probably wondering "What if I want to use some jQuery plugin in my component?" No problem. For brevity, let's modify our user-avatar component to show a tool tip when we hover over the avatar. I've chosen to use the jQuery plugin tooltipster to handle the tooltip. Let's modify the existing code to utilize tooltipster.

Once again, we see the declarative event listener syntax, but for the first time we see this.$. If you are familiar with jQuery, you would expect that we would be querying all the elements with class of 'avatar'. This isn't the case in Ember because context is applied. In our case, we are only looking for elements with the class of 'avatar' in the user-avatar component. It's comparable to jQuery's find method. On destruction of the element, we should unbind the hover event on the avatar and clean up any functionality, this is done by passing 'destroy' to tool tipster. If we go to the browser, refresh and hover an image we should see the user's username.

Conclusion

In this tutorial, we took a deep dive into Ember components and showed how you can take re-usable chunks of UI to generate larger composites and integrate jQuery plugins. We looked at how components are different from views in Ember. We also covered the idea of interface-based programming when it comes to components. Hopefully, I was able to shine some light on not only Ember Components but Web Components and where the Web is headed.