ponyfoo.com

ES6 Template Literals in Depth

Fix
A relevant ad will be displayed here soon. These ads help pay for my hosting.
Please consider disabling your ad blocker on Pony Foo. These ads help pay for my hosting.
You can support Pony Foo directly through Patreon or via PayPal.

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.

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