In the last chapter, we learned about the ember-data library. We learned how easily and effectively the ember-data library design patterns streamline, and how the application talks with the backend API. In this chapter, we will focus on building reusable view components using Ember.js views and component classes.
In this chapter, we shall cover:
In Chapter 3, Rendering Using Templates we learned about handlebars templates and how we can use the Handlebars.js to create data-bound templates. Most of the time, Handlebars.js templates will be enough to create simple applications. But when working on a big, complex application, we are much likely to run into situations in which we can see that we are duplicating a lot of code in handlebars.js templates and repeated handling similar UI events in different controllers of our applications.
In order to tackle the problem of code duplicity and complex event handling of UI elements at one place, Ember.js provides us with two options; components and views. Components and views in Ember.js encapsulate templates, and lets us reuse the abstraction at different places in our application.
Views in Ember.js encapsulate the template and its actions. They have implicit access to the context that the views are being used in. As a result, you can easily access the associated controller and model properties from the view class.
Components, on the other hand, were introduced in Ember in 1.0 RC6 (http://emberjs.com/blog/2013/06/23/ember-1-0-rc6.html). Components, as opposed to views, are totally unaware of the context they are being used in. They don't have access to the enclosing controller, route, or model and require all the data to be passed explicitly to them. This makes the reusability of Ember.js components much better than views. Ember.js components are designed to be reusable, not just within your application, but even across different applications.
Since components were introduced in Ember.js framework, there has been a lot of confusion regarding when to use components and when to use views. Initially, the Ember.js framework contained only the Ember.View
class and all the encapsulation and event handling was done inside it, but when Ember.Component
was introduced, it was introduced to fix the broken abstraction present in Ember.js views.
Since then, the framework and the community have been pushing to use Ember.Component
instead of extending Ember.View
. Even the Ember 2.0 roadmap focuses more on components than it does on views.
As a result, views should be treated as more of an internal implementation detail of Ember.js framework. Views in Ember.js power {{if}}
, {{outlet}}
, and many other Handlebars.js helper methods. You should work more with views if you are creating a new Ember.js element, Handlebar.js helper, or are working on a framework of yours that runs alongside Ember.js.
Because of this shift of the framework toward Ember.Component
, the focus of this chapter will be on Ember components and it will be left to readers to explore ember views for themselves.
Till now, we have been using built-in HTML tags such as body
, tr
, td
, div
, and so on in our application. Wouldn't it be nice if a framework could allow you to build application-specific tags whose behavior and actions could be handled using JavaScript? Ember.js Ember.Component
lets you do that. In fact, the W3C group, which defines the web standards, is already working on a very similar
web component specification that would allow you to define custom application-specific HTML tags. Once browsers support custom components, you should be able to easily migrate your Ember.Component
class to the W3C standards.
The simplest way to define you Ember component is to create a new template file in the app/templates/components/
folder. In order to resolve the components automatically, the Ember.js framework makes it mandatory for the name of the component's file to include a -
. Hence, address
is an invalid component and person-address
is a valid one.
A very common requirement of web applications is to include a copyright footer. Let's create a component to include the company's copyright footer in their application:
app/templates/components
directory called as copyright-footer.hbs
, with the following contents:<footer> <div> © 20014-2015 Ember.js Essentials by Packt Publishing </div> <div> Content is available under MIT license </div> </footer>
The copyright-footer.hbs is present at chapter7/example1/app/templates/components/copyright-footer.hbs
{{copyright-footer}}
in our templates and that would render the copyright footer there.application.hbs
file, present at app/templates/
:<h2 id='title'>Welcome to Ember.js</h2> {{outlet}} {{copyright-footer}}
The application.hbs is present at chapter7/example1/app/templates/application.hbs
This will result in the copyright footer being present in all the pages of our application.
Till now, we saw the case wherein there was static data in our component templates, but to make components reusable, it often requires that some data of the component be made configurable and is passed in to it. This is also one of the main advantages of using components: if you pass in the correct configuration of the components, it works correctly no matter which place the component is included in.
Let's create a new component called as product-description
, which displays the product data passed into it:
product-description.hbs
at app/templates/components/
directory. This template file will contain the components of the product description. Since the data for product specification template changes with the product, this data should be passed in while using the component.<h4> <span> {{name}} </span> </h4> <div> <table> <tr> <td> M.R.P </td> <td> {{MRP}}</td> </tr> <tr> <td> Price </td> <td> {{price}}</td> </tr> <tr> <td> You Save </td> <td> {{sale}} </td> </tr> </table> </div>
The product description component is present at chapter7/example1/app/templates/components/product-description.hbs
As you can see, we have made name
, MRP
, price
, and sale
of a product configurable, and should be passed in explicitly when using the component.
{{product-description name="Product Name" MRP="$ 100" price="$80" sale="$20"}}
You could also fetch the MRP
, price
, and sale
property from your route's model object as follows:
{{#product-description name="Product Name" MRP=model.MRP price=model.price sale=model.sale}}
The model can return the appropriate object by either fetching it from the server, or returning a static object as follows:
import Ember from 'ember'; export default Ember.Route.extend({ model: function(){ return { MRP:"$ 100",price:"$80",sale:"$20"}; } });
The index route returning a static model object is present at chapter7/example1/app/routes/index.js
http://localhost:4200/
on your machine, you will see something like the following:3.15.137.75