ponyfoo.com

ES6 Template Literals in Depth

Yesterday we’ve covered ES6 destructuring in depth, as well as some of its most common use cases. Today we’ll be moving to template literals. What they are, and how we can use them and what good they’re for.

Improve this article
Nicolás Bevacqua
| 11 minute read | 27

Template literals are a new feature in ES6 to make working with strings and string templates easier. You wrap your text in `backticks` and you’ll get the features described below.

  • You can interpolate variables in them
  • You can actually interpolate using any kind of expression, not just variables
  • They can be multi-line. Finally!
  • You can construct raw templates that don’t interpret backslashes

In addition, you can also define a method that will decide what to make of the template, instead of using the default templating behavior. There are some interesting use cases for this one.

Let’s dig into template literals and see what we can come up with.

Using Template Literals

We’ve already covered the basic `I'm just a string`. One aspect of template literals that may be worth mentioning is that you’re now able to declare strings with both ' and " quotation marks in them without having to escape anything.

var text = `I'm "amazed" that we have so many quotation marks to choose from!`

That was neat, but surely there’s more useful stuff we can apply template literals to. How about some actual interpolation? You can use the ${expression} notation for that.

var host = 'ponyfoo.com'
var text = `this blog lives at ${host}`
console.log(text)
// <- 'this blog lives at ponyfoo.com'

I’ve already mentioned you can have any kind of expressions you want in there. Think of whatever expressions you put in there as defining a variable before the template runs, and then concatenating that value with the rest of the string. That means that variables you use, methods you call, and so on, should all be available to the current scope.

The following expressions would all work just as well. It’ll be up to us to decide how much logic we cram into the interpolation expressions.

var text = `this blog lives at ${'ponyfoo.com'}`
console.log(text)
// <- 'this blog lives at ponyfoo.com'
var today = new Date()
var text = `the time and date is ${today.toLocaleString()}`
console.log(text)
// <- 'the time and date is 8/26/2015, 3:15:20 PM'
import moment from 'moment'
var today = new Date()
var text = `today is the ${moment(today).format('Do [of] MMMM')}`
console.log(text)
// <- 'today is the 26th of August'
var text = `such ${Infinity/0}, very uncertain`
console.log(text)
// <- 'such Infinity, very uncertain'

Multi-line strings mean that you no longer have to use methods like these anymore.

var text = (
  'foo\n' +
  'bar\n' +
  'baz'
)
var text = [
  'foo',
  'bar',
  'baz'
].join('\n')

Instead, you can now just use backticks! Note that spacing matters, so you might still want to use parenthesis in order to keep the first line of text away from the variable declaration.

var text = (
`foo
bar
baz`)

Multi-line strings really shine when you have, for instance, a chunk of HTML you want to interpolate some variables to. Much like with JSX, you’re perfectly able to use an expression to iterate over a collection and return yet another template literal to declare list items. This makes it a breeze to declare sub-components in your templates. Note also how I’m using destructuring to avoid having to prefix every expression of mine with article., I like to think of it as “a with block, but not as insane”.

var article = {
  title: 'Hello Template Literals',
  teaser: 'String interpolation is awesome. Here are some features',
  body: 'Lots and lots of sanitized HTML',
  tags: ['es6', 'template-literals', 'es6-in-depth']
}
var {title,teaser,body,tags} = article
var html = `<article>
  <header>
    <h1>${title}</h1>
  </header>
  <section>
    <div>${teaser}</div>
    <div>${body}</div>
  </section>
  <footer>
    <ul>
      ${tags.map(tag => `<li>${tag}</li>`).join('\n      ')}
    </ul>
  </footer>
</article>`

The above will produce output as shown below. Note how the spacing trick was enough to properly indent the <li> tags.

<article>
  <header>
    <h1>Hello Template Literals</h1>
  </header>
  <section>
    <div>String interpolation is awesome. Here are some features</div>
    <div>Lots and lots of sanitized HTML</div>
  </section>
  <footer>
    <ul>
      <li>es6</li>
      <li>template-literals</li>
      <li>es6-in-depth</li>
    </ul>
  </footer>
</article>

Raw templates are the same in essence, you just have to prepend your template literal with String.raw. This can be very convenient in some use cases.

var text = String.raw`The "\n" newline won't result in a new line.
It'll be escaped.`
console.log(text)
// The "\n" newline won't result in a new line.
// It'll be escaped.

You might’ve noticed that String.raw seems to be a special part of the template literal syntax, and you’d be right! The method you choose will be used to parse the template. Template literal methods – called “tagged templates” – receive an array containing a list of the static parts of the template, as well as each expression on their own variables.

For instance a template literal like `hello ${name}. I am ${emotion}!` will pass arguments to the “tagged template” in a function call like the one below.

fn(['hello ', '. I am', '!'], 'nico', 'confused')

You might be confused by the seeming oddity in which the arguments are laid out, but they start to make sense when you think of it this way: for every item in the template array, there’s an expression result after it.

Demystifying Tagged Templates

I wrote an example normal method below, and it works exactly like the default behavior. This might help you better understand what happens under the hood for template literals.

If you don’t know what .reduce does, refer to MDN or my “Fun with Native Arrays” article. Reduce is always useful when you’re trying to map a collection of values into a single value that can be computed from the collection.

In this case we can reduce the template starting from template[0] and then reducing all other parts by adding the preceding expression and the subsequent part.

function normal (template, ...expressions) {
  return template.reduce((accumulator, part, i) => {
    return accumulator + expressions[i - 1] + part
  })
}

The ...expressions syntax is new in ES6 as well. It’s called the “rest parameters syntax”, and it’ll basically place all the arguments passed to normal that come after template into a single array. You can try the tagged template as seen below, and you’ll notice you get the same output as if you omitted normal.

var name = 'nico'
var outfit = 'leather jacket'
var text = normal`hello ${name}, you look lovely today in that ${outfit}`
console.log(text)
// <- 'hello nico, you look lovely today in that leather jacket'

Now that we’ve figured out how tagged templates work, what can we do with them? Well, whatever we want. One possible use case might be to make user input uppercase, turning our greeting into something that sounds more satirical – I read the result out loud in my head with Gob’s voice from Arrested Development, now I’m laughing alone. I’ve made a huge mistake.

function upperExpr (template, ...expressions) {
  return template.reduce((accumulator, part, i) => {
    return accumulator + expressions[i - 1].toUpperCase() + part
  })
}
var name = 'nico'
var outfit = 'leather jacket'
var text = upperExpr`hello ${name}, you look lovely today in that ${outfit}`
console.log(text)
// <- 'hello NICO, you look lovely today in that LEATHER JACKET'

There’s obviously much more useful use cases for tagged templates than laughing at yourself. In fact, you could go crazy with tagged templates. A decidedly useful use case would be to sanitize user input in your templates automatically. Given a template where all expressions are considered user-input, we could use insane to sanitize them out of HTML tags we dislike.

import insane from 'insane'
function sanitize (template, ...expressions) {
  return template.reduce((accumulator, part, i) => {
    return accumulator + insane(expressions[i - 1]) + part
  })
}
var comment = 'haha xss is so easy <iframe src="http://evil.corp"></iframe>'
var html = sanitize`<div>${comment}</div>`
console.log(html)
// <- '<div>haha xss is so easy </div>'

Not so easy now!

I can definitely see a future where the only strings I use in JavaScript begin and finish with a backtick.

Liked the article? Subscribe below to get an email when new articles come out! Also, follow @ponyfoo on Twitter and @ponyfoo on Facebook.
One-click unsubscribe, anytime. Learn more.

Comments (27)

SylvainPV wrote

This is the least attractive feature of ES6 to me. I do not understand where is the improvement between `this blog lives at ${host}` and "this blog lives at "+host . I never had a problem with multiline strings in 10 years of JavaScript, but now a line break in my code has a different meaning whether it is inside a backticked string or not, and I have to remember that. Template tags are such a weird way to call functions, I thought it was a typo when I first saw them. What is wrong with plain old functions calls ? Why can’t we pass a function with string arguments instead ? Now with this backstick character which is such a pain to type on non-US keyboards, we have three different characters to define a String litteral. So, no thanks ES6, I won’t use template strings.

By the way, the backstrick character is in conflict with Markdown notation for inline code. Enjoy the trouble with all the Markdown-based editors (like this one).

Barney Carroll wrote

Enjoy the trouble with all the Markdown-based editors (like this one).

Thanks, I will.

let templates = `have a chance`;

How did that work? Let’s space-indent this block to have a look:

```Javascript
let templates = `have a chance`;
```
SylvainPV wrote

Actually the problem was inline code, but Markdown provides a solution ; you have to surround inline code with two backticks instead of one when `code contains template strings`.

`` `code contains template strings` ``
Nicolas Bevacqua wrote

I did not know this. let text = `just trying it out. ${thanks}` Cool! In my articles I just wrapped those uses with <code>, which was pretty clumsy.

Barney Carroll wrote

You may be interested in t7, a library that takes the markup example to its natural conclusion as a virtual DOM DSL. In turn, the author wrote Inferno to provide an API that looks like React for using t7 to make use of these templates. The beauty of Inferno is that unlike other virtual DOM diffing libraries, it only diffs the interpolated expressions in the strings, leading to much lower overhead.

anon wrote

Hope this frees us from the pompous hype that is React.

I’m tired of their inability to explain their abstractions.

Mohsen Azimi wrote

One thing thing that expression can not have a if condition. So you can’t write like this:

let s = `
 ${if (window) { "in browser"}
`

But it’s possible to hack tagged templates to achieve a ifCond function that returns a tag function based on the condition:

function ifCond (condition) {
  return condition ? normal : empty
}
  
function empty(){
  return '';
}
function normal (template, ...expressions) {
  return template.slice(1).reduce((accumulator, part, i) => {
    return accumulator + expressions[i] + part
  }, template[0])
}

  
const result = `
 ${ifCond(true)`Answer is ${Math.random()}`}
`;

console.log(result);
  
Nicolas Bevacqua wrote

That’s the same limitation as with JSX templates, you could use an expression like this instead.

var text = `Your name is ${condition && name || 'unknown'}`
Barney wrote

This is exactly what ternary expressions are for.

var text = `Your name is ${condition ? name : 'unknown'}`

In more complex situations, you can always resort to immediately-invoked function expressions. These can be pretty terse with arrow functions, but not quite intuitive:

`blah ${(()=>{
  if( x ) return `foo`;
  else if( y ) return `bar`;
  else return `baz`
}())} blah`
anon wrote

heh… heh…

sure, the man in the $5000 suit will read your article…

COME ON!

Nicolas Bevacqua wrote

Hahahahahaha. This comment made my day.

JohnSz wrote

I dislike writing debugging code like:

console.log('x='+x+' y='+y}
console.log(`x=${x} y=${y}`)

I happy with an idea I first saw from Axel Rauschmayer which lets you write:

console.log(vars `${{x,y}}`) // 'x=100, y=[200,201]'

Here’s my implementation:

export function vars (templateStrings, ...substitutions) {
 return substitutions.reduce( (accumi, parti, i) => {
   let concatenable = typeof parti !== 'object'  ? parti : Object.keys(parti).reduce(
       (accumj, partj, j) => accumj + (j > 0 ? ', ' : '') + partj + '=' + JSON.stringify(parti[partj])
       , '');
   return accumi + concatenable + templateStrings[i + 1];
 }, templateStrings[0]);
}
Nicolas Bevacqua wrote

You could just do console.log({x}) in ES6 because shorthand property assignment.

JohnSz wrote

Your suggestion was what I had tried first. :)

For var x=1, y=[1,2], z={zz:1}; console.log({x,y,z}) the latest version of Chrome gives > Object {x: 1, y: Array[2], z: Object} at best and just > Object at worst, depending on the OS. The console output is also truncated when the output string is too long.

The template gets around this.

Nomaed wrote

I rather like the syntax of

console.log('x=%s, y=%s', x, y);

Of courser %s, %d or %o where needed.

Zlatko wrote

Well, I’ve read it a few times, and even after your article, I still didn’t get the tagged template. Then looking at your examples and orders (normal fn, then upperExpr…) cleared it a bit. It helps you go through each variable in the template and operate on it, right? Replacing:

var str = 'regular'; `This is a ${str} thing.` `This one should upppercase stuff: ${str.toUpperCase()}.`

So with tagged templates, you get the chance to work on each of those vars before you interpolate them.

Right?

Zlatko wrote

No “edit comment” to fix my code sample, but yeah, the comment was just me thinking aloud and in public, so feel free to ignore it in any case :)

Boris wrote

Why are you using slice? Here is exactly the same implementation of normal function:

function normal (template, ...expressions) {
  return template.reduce((accumulator, part, i) => {
    return accumulator + expressions[i] + part
  });
}

If reduce function is called without initial value, first item from array will be used instead. So you can do the same thing but a bit easier.

Nicolas Bevacqua wrote

The output of doing what you suggest is not what you think:

function normal (template, ...expressions) {
  return template.reduce((accumulator, part, i) => {
    return accumulator + expressions[i] + part
  })
}
var name = 'nico'
var outfit = 'leather jacket'
var text = normal`hello ${name}, you look lovely today in that ${outfit}`
console.log(text)
// <- 'hello leather jacket, you look lovely today in that undefined'

Unless you expected to be called a leather jacket, that is.

Boris wrote

Well, I missed, that i would be started from 1. So, it can be [easily fixed](http://babeljs.io/repl/#?experimental=false&evaluate=true&loose=false&spec=false&code=function normal (template%2C …expressions) { return template.reduce((accumulator%2C part%2C i) %3D> { return accumulator %2B expressions[i - 1] %2B part })%0A%7D%0Avar%20name%20%3D%20’nico’%0Avar%20outfit%20%3D%20’leather%20jacket’%0Avar%20text%20%3D%20normal%60hello%20%24%7Bname%7D%2C%20you%20look%20lovely%20today%20in%20that%20%24%7Boutfit%7D%60%0Aconsole.log(text)&utm_content=buffer49995&utm_medium=social&utm_source=twitter.com&utm_campaign=buffer)

function normal (template, ...expressions) {
  return template.reduce((accumulator, part, i) => {
    return accumulator + expressions[i - 1] + part
  })
}
var name = 'nico'
var outfit = 'leather jacket'
var text = normal`hello ${name}, you look lovely today in that ${outfit}`
console.log(text)
Nicolas Bevacqua wrote

That does look simpler! :)

Charles Rector wrote

Sidestepping the indexing shenanigans can also be nice:

function normal (template, ...expressions) {
  return template.reduce((accumulator, part) => {
    return accumulator + expressions.shift() + part
  })
}
Steven wrote

This is great! I didn’t even know about the tagged templates part. Perfect for sanitizing user input. Thanks!

Also, it might be worth mentioning that you can begin using ES6 now if you compile to ES5 with something like Babel or TypeScript.

vsemozhetbyt wrote

If we will need to process (toUpperCase etc) not the template strings, but the expressions, the simpler version will not fit, only the version with splice:

function upperExpr (template, ...expressions) {
  return template.slice(1).reduce((accumulator, part, i) => {
    return accumulator + expressions[i] + part.toUpperCase()
  }, template[0].toUpperCase())
}

Otherwise we will need to .toUpperCase accumulator, and that is wrong.

Steven wrote

After reading this post and the comments, I came up with a type safe way of implementing templates. It’s like Handlebars for TypeScript. See typed-tmpl repo.

vsemozhetbyt wrote

Sorrry, the correct first sentence of my comment above is:

“If we will need to process (toUpperCase etc) not the expressions, but the template strings, the simpler version will not fit, only the version with splice:”