Key Takeaways
- Handlebars 4.0 introduced Inline Partials and Decorators as major new features. Inline Partials allow for the creation of templates that can be reused, while Decorators enable the modification of states before rendering a template.
- Inline Partials are defined within templates and require no JavaScript to register them. They are block-scoped, meaning they can only be used within the current scope and any below it. They are ideal for small, reusable chunks of HTML code.
- Decorators are used to modify the Handlebars program function and control the Handlebars runtime before execution. They can be used to alter context data or helpers.
- The use of Decorators can simplify and optimize code, making it more modular, extensible and testable. They can be particularly useful for tasks such as formatting money.
- Both Inline Partials and Decorators are powerful tools for front-end development, making code cleaner, more organized and potentially more efficient.
Inline Partials
Partials are a common templating concept not unique to Handlebars. The idea behind it is to create templates that are likely to be re-used, separate them into their own file (a Partial), and then use them in different templates. You may think at Partials as a simple a tool to modularize your templates. In Handlebars, Partials might not be the most convenient structure to work with. First of all, all partials are global. That means this tool may work for your application, but having little control over it could become a problem in large applications. Secondly, partials are required to be registered using JavaScript. Many template pre-compilers or loaders will handle this for you, callingHandlebars.registerPartial()
. Finally, partials have to be separated from the templates where they’re being used. This can be a boon if your templates are large, but can also make it difficult for developers to fully understand the output of a template. They’ll need to switch between many different files before understanding the full output.
All these issues shape the way developers use partials. They end up being reserved just for the largest chunks of reusable code.
With Inline Partials, Handlebars finally releases the true potential of Partials, enabling you to drop JavaScript and the necessity to split partials into separate files. Inline Partials are defined inside your templates, in Handlebars syntax. There is no JavaScript required to register them. You just declare a partial and use it. In addition, they aren’t global but block-scoped. This means that once you’ve declared an Inline Partial in your template, it can only be used in the current scope and any below it.
When deciding to use an Inline Partial or a normal Partial, look for small, re-usable chunks of HTML code that have either of these properties:
- They’re too small to deserve to be in their own partial file.
- They are (or can be) used just in the context of a single template.
Using Inline Partials
Now let’s take a look at Inline Partial syntax and usage. Here’s how you declare an Inline Partial. First, take the code you want to be your partial.<li>I'm iteration #{{number}}</li>
Then wrap it with the new inline syntax, passing one argument which is the name of the partial.
{{#* inline "iterationCount"}}
<li>I'm iteration #{{number}}</li>
{{/inline}}
You can now use this partial in the Handlebars template where it was declared. Here’s a complete example.
{{#* inline "iterationCount"}}
<li>I'm iteration #{{number}}</li>
{{/inline}}
{{#each someArray}}
{{> iterationCount}}
{{/each}}
Simple Partials Example
With the previous explanation in mind, the next step is to understand how we would use Partials before we had Inline Partials. Let’s assume we start with this template:// template.hbs
<h1>Hello {{firstName}} {{lastName}}</h1>
<ul>
{{#each clients}}
<li>{{firstName}} {{lastName}}</li>
{{/each}}
</ul>
The repetition of {{firstName}} {{lastName}}
opens up the opportunity for typos and errors. The task to accomplish is to extract that pattern into a partial, so let’s see what we have to do in order to achieve it.
First of all, you create a JavaScript file, for example someFile.js
, with the following code:
Handlebars.registerPartial('fullName', '{{firstName}} {{lastName}}');
Then, in you Handlebars template you can have:
<h1>Hello {{> fullName}}</h1>
<ul>
{{#each clients}}
<li>{{> fullName}}</li>
{{/each}}
</ul>
While this does clean up our template and make it more idiomatic, it obfuscates the implementation of the fullName
partial into a separate file (using a different language and syntax). A developer coming to this code for the first time might face some troubles trying to understand an entire template if many of these small chunks of templates were refactored into partials.
Inline Partials Example
Now let’s take the previous example and solve it with Inline Partials. You’ll notice a few things in the next example:- Everything is in the same file and same language.
- Template scoped partial means you can have a different “full name” format in another file.
- Keep the same benefits of normal Partials, such as removing redundancies.
// template.hbs
{{#* inline "fullName"}}{{firstName}} {{lastName}}{{/inline}}
<h1>Hello {{> fullName}}</h1>
<ul>
{{#each clients}}
<li>{{> fullName}}</li>
{{/each}}
</ul>
Decorators
In the introduction of this article I’ve mentioned another big feature in Handlebars version 4.0, Decorators. Decorators let you “decorate” the Handlebars program function and modify states before rendering a template. The primary goal is to allow you to use non-output “metadata” to add functionality to your templates. The implementation is based on Yehuda Katz’s JavaScript Decorator proposal for ES6. In many ways, Decorators in Handlebars provide you with a companion to helper functions at a more fundamental level. In fact, before their introduction, you may have been using helpers to achieve what now is elegantly done by Decorators. To understand where Decorators fit in Handlebars template rendering, let’s have a look at how Handlebars compiles templates. The “Handlebars Dance”, as I like calling it, does something like:- Getting the template
- Compiling the template
- Rendering an output
Handlebars.compile
function. It takes a template as a string and compiles it, returning a function that you can then call with some context data (the third step from above). Each block in your Handlebars template creates one of these compiled functions, and the main one that is returned calls them as needed to render your output.
Decorators interject themselves into these block-scoped compiled functions, giving you control to execute some functionality before the block is rendered. What you do with it is up to you, but the return value that a Decorator expects is a function that would render a template output.
Before looking at the Decorator function arguments, let’s examine a simple instance.
Using Decorators
Decorators are registered in JavaScript, like helpers and partials (not Inline ones, though!). Here’s an example of that:Handlebars.registerDecorator('shhh', function(program, props, container, context) {
var isLoud = program().trim() === 'loud';
if (isLoud) {
return function() { return ''; };
} else {
return program;
}
});
In the above example, we look at the Handlebars program function (I usually call this “the compiled function”). If the program returns “loud”, then we will overwrite it with a function that returns an empty string. Otherwise, we will return the normal program function.
Let’s see how this Decorator will be used:
loud
{{*shhh}}
With this template example, the original program function will return “loud” (Decorators have no output). And the output of this template when rendered will be:
That’s right, just an empty string.
The function whose scope is to render the template that has been “decorated”, which is returned from the “shhh” Decorator, returns an empty string. That function is returned based on the truthiness of “loud”.
Let’s now look at a different template:
quiet
{{*shhh}}
The output when rendering this template would be:
quiet
Since the program did not match “loud”, it was passed through instead of being overwritten.
This is an extremely arbitrary example, but hopefully you can see how Decorators affect the program function and how powerful is having control over it. It’s now time to see the Decorator function arguments.
Decorator Function Arguments
When a function registered as a Decorator is called by Handlebars, a set of arguments are passed to it. We’ll examine each of them in the following sections, so you can understand what you’re able to decorate with Decorators. Here’s the complete function signature for a Decorator function:function(program, props, container, context)
Decorator function return value
Decorators must return a function or falsy value (undefined
, null
, false
, and so on). Any string or object returned will throw an exception. The function returned will be used to render the finished Handlebars string. If undefined
is returned, the original program argument will be used implicitly.
program
This is the compiled Handlebars function that is passed data, and returns a rendered string. You can modify the arguments, the return value, or adjust the context when the function is called. Return this program argument to let the rendering pass through the Decorator. You can also “overwrite” the program argument by returning a different function.
props
Any properties set on this object will be set on the program function even if the program function is replaced. This is a safe place to set metadata that you want to access in other Decorators or helpers.
container
This is the current Handlebars runtime container. This has all the partials, helpers, and context data and can be modified (as you’ll see in the example below).
context
This is the parent context of your template, which includes any arguments to the Decorator as well as the data that was passed into the program function.
Formatting Money in Handlebars Pre-4.0
To demonstrate Decorators in the real world, let’s take a look at a template use case you may be familiar with: formatting money. We want to find a simple way to dynamically format a given value for a given currency. Handlebars does provide some existing mechanisms to solve this. Let’s look at a way to solve this problem with pre-4.0 Handlebars features. First we create the helper to format money. The helper will accept the value to format and the currency as arguments://someFile.js
Handlebars.registerHelper('formatMoneyHelper', function(value, currency) {
switch(currency) {
case 'USD':
return new Handlebars.safeString('$' + value + 'USD');
case 'EUR':
return new Handlebars.safeString('€' + value + 'EUR');
}
});
Now we can use this helper in a template.
//template.hbs
Starting amount: {{formatMoneyHelper this.start this.format}}
Ending amount: {{formatMoneyHelper this.end this.format}}
Profit/Loss: {{formatMoneyHelper this.net this.format}}
We would expect our data to be in this format:
{
start: 12.30,
end: 15.30,
net: 3.00,
format: 'USD'
}
This isn’t a bad way to solve this. Helpers are designed for this kind of problems, but there is a lot of redundant code being written, both in the template and the helper. We might make more optimizations with this, but let’s examine the way to accomplish this task using Decorators in Handlebars 4.0 instead.
Formatting Money with Decorators in Handlebars 4.0
A better way to format money is to have a simpler helper that just takes the value. It should already understand what currency money should be formatted in. To do this in a dynamic way would be tricky with helpers, so let’s make use of Decorators to find a simpler solution. Since Decorators are able to modify the main program function, let’s create a Decorator to set up a format helper function that will already have the currency loaded in. We’ll start with the JavaScript and Decorator registration.function formatUSD(value) {
return new Handlebars.safeString('$' + value + 'USD');
}
function formatEUR(value) {
return new Handlebars.safeString('€' + value + 'EUR');
}
Handlebars.registerDecorator('activateFormatter', function(program, props, container, context) {
var moneyHelper,
format = context.args[0] || context.data.root.format;
switch(format) {
case "USD":
moneyHelper = formatUSD;
break;
case "EUR":
moneyHelper = formatEUR;
break;
default:
console.log('Money format not set. Please set before rendering template.');
moneyHelper = function() {};
}
container.helpers = {
formatMoneyHelper: moneyHelper
};
});
The Decorator takes care of registering the right formatting helper based on a static value or a format property in our context object, allowing it to be dynamic in loops as well. This makes our helper function to be much more modular and extensible. A side benefit about this approach is the testability of the formatting functions, since they are regular JavaScript.
Next let’s see how we can use this Decorator in our template:
//template.hbs
{{* activateFormatter}}
Starting amount: {{formatMoneyHelper this.start}}
Ending amount: {{formatMoneyHelper this.end}}
Profit/Loss: {{formatMoneyHelper this.net}}
This will use the format property in our context object to set the formatUSD function as our formatMoneyHelper helper function. We can override it as well, using this syntax:
{{* activateFormatter "EUR"}}
The implementation using Decorators is more elegant, testable, and allows you to control the formatting of the current block inside your template.
Decorators are incredibly powerful, and the above example is just a peek at what can be accomplished.
Conclusions
Hopefully this article has inspired you to use Inline Partials and Decorators in your own projects employing Handlebars. You’ve seen how Inline Partials are useful to define partials in your template and reduce the amount of JavaScript overhead to register partials. Moreover, you’ve seen that they’re perfect for small, repeating pieces of markup. On the other hand, Decorators allow you to modify the existing Handlebars block program function and give you control over the Handlebars runtime before execution. They’re perfect to mess with context data or helpers. Therefore both Inline Partials and Decorators are powerful additions to an already essential tool for front-end development. Now go forth and Decorate your Handlebars templates with Inline Partials!Frequently Asked Questions (FAQs) about Handlebars 4.0
What are the benefits of using inline partials in Handlebars 4.0?
Inline partials in Handlebars 4.0 provide a way to define a partial within the same file as your template. This can be particularly useful when you have a small piece of a template that you want to reuse within that same template. It keeps your code cleaner and more organized, and it can also improve performance by reducing the number of files that need to be loaded.
How do decorators work in Handlebars 4.0?
Decorators in Handlebars 4.0 are functions that can be used to modify the template context, or the way that Handlebars evaluates expressions. They can be used to add, remove, or modify properties on the context object, or to change the way that Handlebars looks up properties on the context object. This can be useful for adding custom functionality or behavior to your templates.
How do I register a partial in Handlebars?
To register a partial in Handlebars, you can use the Handlebars.registerPartial
method. This method takes two arguments: the name of the partial, and the template source. The template source can be a string of HTML, or a compiled template. Once a partial is registered, it can be used in any template by referencing its name.
What is the difference between a partial and a helper in Handlebars?
A partial in Handlebars is a reusable piece of a template, while a helper is a function that can be used within a template to manipulate data or generate HTML. Partials are useful for reusing large chunks of markup, while helpers are useful for performing complex logic or transformations on your data.
How do I use a partial with a custom context in Handlebars?
To use a partial with a custom context in Handlebars, you can pass the context as the second argument to the partial call. For example, {{> myPartial myContext}}
would render the myPartial
partial with myContext
as the context. This can be useful for rendering a partial with a subset of your data, or with a modified version of your data.
How do I create a block helper in Handlebars?
To create a block helper in Handlebars, you can use the Handlebars.registerHelper
method. This method takes two arguments: the name of the helper, and a function that defines the helper’s behavior. The function will be passed any arguments that are used with the helper in the template, as well as an options object that contains a fn
method for rendering the block.
How do I use a decorator in Handlebars?
To use a decorator in Handlebars, you can use the Handlebars.registerDecorator
method. This method takes two arguments: the name of the decorator, and a function that defines the decorator’s behavior. The function will be passed the current context and the options object. Once a decorator is registered, it can be used in any template by referencing its name.
How do I compile a template in Handlebars?
To compile a template in Handlebars, you can use the Handlebars.compile
method. This method takes a string of HTML as its argument, and returns a function that can be used to render the template with a given context. The returned function takes one argument: the context object to use when rendering the template.
How do I precompile templates in Handlebars?
To precompile templates in Handlebars, you can use the Handlebars.precompile
method. This method takes a string of HTML as its argument, and returns a string of JavaScript code that can be used to render the template. This can be useful for improving performance, as it allows you to compile your templates ahead of time, rather than at runtime.
How do I handle errors in Handlebars?
Handlebars provides several ways to handle errors. If an error occurs while compiling a template, Handlebars will throw an exception. You can catch this exception using a try/catch block, and handle the error as appropriate. If an error occurs while rendering a template, Handlebars will replace the erroneous expression with an empty string, and log a warning to the console. You can customize this behavior by providing a custom logger to Handlebars.
I'm a web engineer at Expedia who specializes in JavaScript on both ends. I've taught others how to code at the University of Washington and Seattle Central College. Currently, I'm teaching online with Pluralsight and speaking at conferences.