Ember Templates — Handlebars Syntax and Rendering
In this tutorial, you will learn about Ember Templates. We cover key concepts, practical examples, and best practices to help you master this topic.
Ember templates use Handlebars, a logic-less templating language that separates presentation from logic. Templates access data from routes and components using {{}} expressions, handle conditionals with {{if}} and {{unless}}, and iterate with {{each}}.
What You'll Learn
You will learn Handlebars syntax, template expressions, built-in helpers, conditional rendering, looping, component invocation, and template composition in Ember.
Why It Matters
Templates are what users see. Clean, readable templates make the application maintainable. Handlebars enforces logic-less templates, keeping business logic in JavaScript and presentation in templates.
Real-World Use
A data dashboard template renders charts, tables, and metrics using {{each}} for data iteration, {{if}} for conditional display, and custom helpers for date formatting and data transformation.
flowchart LR
A[Route/Component] --> B[Context data]
B --> C[Template]
C --> D[{{each}} loop]
C --> E[{{if}} conditional]
C --> F[{{helper}}]
C --> G[{{component}}]
D --> H[Rendered HTML]
Expressions
Use double curly braces to output values:
{{! app/templates/profile.hbs }}
<h1>{{this.user.name}}</h1>
<p>Email: {{this.user.email}}</p>
<p>Member since: {{this.user.createdAt}}</p>
<p>Total posts: {{this.user.postCount}}</p>
Conditionals: if and unless
{{! app/templates/user-card.hbs }}
<div class="user-card">
<h3>{{@user.name}}</h3>
{{#if @user.isActive}}
<span class="badge active">Active</span>
{{else if @user.isPending}}
<span class="badge pending">Pending</span>
{{else}}
<span class="badge inactive">Inactive</span>
{{/if}}
{{#unless @user.isBlocked}}
<button type="button" {{on "click" @this.sendMessage}}>
Send Message
</button>
{{/unless}}
</div>
Loops: each
{{! app/templates/posts.hbs }}
<h1>Posts ({{this.model.length}})</h1>
{{#each this.model as |post index|}}
<article class="post-card">
<span class="post-number">#{{add index 1}}</span>
<h2>
<LinkTo @route="post" @model={{post.id}}>
{{post.title}}
</LinkTo>
</h2>
<p class="post-meta">
By {{post.author.name}} on {{format-date post.createdAt}}
</p>
<p class="post-excerpt">{{post.excerpt}}</p>
</article>
{{else}}
<div class="empty-state">
<p>No posts yet.</p>
</div>
{{/each}}
Local Variables with let
{{! app/templates/product.hbs }}
{{#let (multiply @product.price (add 1 @product.taxRate)) as |totalPrice|}}
<div class="product-card">
<h3>{{@product.name}}</h3>
<p>Base price: ${{@product.price}}</p>
<p>Tax: {{multiply @product.taxRate 100}}%</p>
<p class="total">Total: ${{totalPrice}}</p>
</div>
{{/let}}
Component Invocation
Use angle bracket invocation for components:
{{! app/templates/application.hbs }}
<header>
<NavBar @session={{this.session}} />
</header>
<main>
{{outlet}}
</main>
<footer>
<FooterLinks />
</footer>
Element Modifiers
Use {{on}} and {{did-insert}} modifiers:
{{! app/components/click-counter.hbs }}
<button
type="button"
{{on "click" this.increment}}
{{did-insert this.setup}}
>
Clicked {{this.count}} times
</button>
// app/components/click-counter.js
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
export default class ClickCounterComponent extends Component {
@tracked count = 0;
@action
increment() {
this.count++;
}
@action
setup(element) {
console.log('Button rendered:', element);
}
}
Built-in Helpers
Ember provides several built-in helpers:
{{! Helper examples }}
<p>Current year: {{year}}</p>
<p>Capitalized: {{capitalize @name}}</p>
<p>Joined: {{join ', ' @items}}</p>
<p>HTML safe: {{{@htmlContent}}}</p>
{{! Equality check }}
{{#if (eq @status 'active')}}
<span>Active</span>
{{/if}}
{{! Math }}
<p>Total: {{add @base @tax}}</p>
<p>Discount: {{multiply @price 0.9}}</p>
{{! Array helpers }}
{{#each (filter-by 'isActive' @users) as |user|}}
<li>{{user.name}}</li>
{{/each}}
Yielding Content
Components can yield content using {{yield}}:
{{! app/components/card.hbs }}
<div class="card {{if @padding 'card-padded'}}">
<div class="card-header">
{{@title}}
</div>
<div class="card-body">
{{yield}}
</div>
</div>
{{! Usage }}
<Card @title="User Info" @padding={{true}}>
<p>Name: {{user.name}}</p>
<p>Email: {{user.email}}</p>
</Card>
Template Only Components
Simpler components do not need a JavaScript file:
{{! app/components/status-badge.hbs }}
{{! No .js file needed }}
<span class="badge badge-{{@type}}">
{{@text}}
</span>
Common Mistakes
- Using
{{this.model.property}}instead of{{this.model.property}}. Always usethis.for controller/component properties and@for arguments. - Forgetting
{{else}}in each loops for empty states. Without a block, empty collections render nothing visible. Always include an empty state template. - Using triple
{{{}}}without sanitizing. Triple braces disable HTML escaping. Only use them with trusted content. - Putting complex logic in templates. Helpers should handle transformations. Templates should only display data.
- Not using angle bracket invocation for components. Old
{{component-name}}syntax is deprecated. Use<ComponentName />.
Practice Questions
- How do you output a property from a component in a template?
- What is the difference between
{{if}}and{{unless}}? - How do you render a list of items with an empty state?
- What is the purpose of
{{yield}}? - Challenge: Create a template that displays a list of products with images, prices, and stock status. Use
{{each}}with empty state,{{if}}for in-stock/out-of-stock labels,{{let}}for computed price with tax, and a custom helper for currency formatting.
FAQ
Mini Project
Create a product catalog template that renders a grid of product cards. Each card shows product name, price (formatted with currency helper), stock status badge, and an add-to-cart button. Use {{each}} with empty state, {{if}} for stock status, {{let}} for price with tax, and a custom currency helper. Style with co-located CSS.
What's Next
Now that you understand templates, learn Ember Components for building reusable UI. Then explore Ember Component Lifecycle for lifecycle management.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro