Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

ES6 Template Literals and Tagged Templates

DZone's Guide to

ES6 Template Literals and Tagged Templates

If you're interested in learning more about the new upgrades to ES6, read on to see how to use CoffeeScript to get the most out of it.

· Web Dev Zone
Free Resource

Prove impact and reduce risk when rolling out new features. Optimizely Full Stack helps you experiment in any application.

String concatenation has always been a pain in the ass for web developers and especially if you are writing JavaScript. You tend to frequently forget the + signs in between the variables and the user strings. Many server-side languages have much more flexible string concatenation systems built in. Scala and Groovy are ones that I know about that are currently on the JVM (Java Virtual Machine). Although CoffeeScript has supported this new type of string concatenation for a long time, recently ES6 has started to support them too. In ES6, they are known as Template Literals.

What do Template Literals look like? Consider an example where you need to create a combo box of people, from a provided data set.

const people = [
  {
    name: 'Manvendra Singh',
    id: 'manvendrask'
  },
  {
    name: 'Kirti Nandwani',
    id: 'knandwani'
  },
  {
    name: 'Brij Kishor',
    id: 'hackishor'
  }
];

const options = people.map(person => '<option value="' + person.id + '">' + person.name + '</option>').join('');
const html = '<select name="people" id="people">' + options + '</select>';

document.querySelector('#people_div').innerHTML = html; # Given we have some div with id of people_div on it.

Do you see how we are creating the option elements and finally the selectelement? Isn’t that difficult? Can you understand that? Can you write that? I can't! I myself faced many difficulties in creating those option elements. I was constantly confused between those "double quotes" and 'single quotes'.

Let’s see now how the new Literal Templates feature can rescue us here:

const options = people.map(person => `<option value="${person.id}">${person.name}</option>`).join('');
const html = `<select name="people" id="people">${options}</select>`;

document.querySelector('#people_div').innerHTML = html; # Given we have some div with id of people_div on it.

Can you get it now? Let me explain. Literal Templates are the strings which are surrounded by a pair of backticks( `...`) instead of the double or single quotes. Between these backticks, we can write our user string and put the variables inside a pair of curly braces which are preceded by a dollar sign.

Can you get it now? Of course, you can! By the way, I’m using a joinmethod on the return value of the map function. This is because, by default, array elements are converted to comma separated strings when used inside a string operation.

Here is the output by the way:

Template literals select output

But, what are these Tagged Templates things? It’s a way of passing the Template Literal to a user defined function that returns the final string. Let’s look at an example:

const string = `This string contains a \t tab and\n one new line character.`;
console.log(string);
# Output
# This string contains a   tab and
#  one new line character.

As the output shows, the JavaScript engine processes the special characters. What if we didn’t want those special characters to be expanded? Consider the following:

const string = String.raw`This string contains a \t tab and\n one new line character.`;
console.log(string);
# Output
# This string contains a \t tab and\n one new line character.

What happened? What is that String.raw doing there before our Template Literal?

My friend, that String.raw is called a Tag Function. This Tag Function processes the Template Literal and returns a new string. Getting it now? What is happening here is that there is a raw function defined on the String object. The code snippet below shows its signature and how it’s being internally called by the JavaScript runtime while processing the Template Literal.

String.prototype.raw = (strings, ...values) => {
  # strings is an array
  # ...values is also an array that contains all of the rest parameters passed to this function.
};

String.raw(["This string contains a \t tab and\n one new line character."]);

To understand more on this function signature, let's consider the following example where we would be creating our Tagging Function to return the same string which would otherwise be returned by the JavaScript runtime (we are just mimicking the built-in behavior):

const taggingFunc = (strings, ...values) => strings.reduce((sentence, string, index) => `${sentence}${string}${values[index] || ''}`, '');

const name = 'Manvendra Singh';
const id = 'manvendrask';
const introduction = taggingFunc`Hello I'm ${name}, and my id is ${id}`;
console.log(introduction); // Hello I'm Manvendra Singh, and my id is manvendrask

# It's how JavaScript runtime have called this taggingFunc
# taggingFunc(["Hello I'm ", ", and my id is ", ""], name, id);

Let’s see what’s happening here. The taggingFunc receives two parameters, the first one contains an array that contains all of the user defined string literals, as can be seen from the last comment, and the second one contains the rest of the variables values, which are also an array, but it contains values at the runtime because there can be any number of arguments from the Template Literal.

Here, we are just building a whole string by using the Array.reduce method, along with adding the values from the values array.

Here are two things to notice though:

  1. The strings variable will always be one size larger than the values.
  2. If the variables substitution ends the Template Literal, then the strings array contains an empty string value at the end, and if the variable substitution starts the Template Literal then the strings array contains an empty string value at the start.

Still confused? Let’s look at an example:

function tagFunc(strings, ...values) {

}

# tagFunc(["Hello this is a variable at the ", ""], "end");
const string = tagFunc`Hello this is a variable at the ${'end'}`;

# tagFunc(["", " this line with a variable"], "Starting");
const string2 = tagFunc`${'Starting'} this line with a variable`;

As you can see from the comments, the strings and values variables are populating. It’s simple and straightforward.

Let’s go through one more example. Suppose we want to build up a custom introductory string for a web developer for replacing and abbreviating the acronyms using the <abbr> HTML tag.

const dictionary = {
  JS: "JavaScript",
  HTML: "Hyper Text Markup Language",
  CSS: "Cascading Style Sheets"
};

function abbreviate(strings, ...values) {
  const abbreviations = values.map(value => {
    if (dictionary[value]) {
      return `<abbr title="${dictionary[value]}">${value}</abbr>`
    }
    return value;
  });

  return strings.reduce((sentence, string, i) => `${sentence}${string}${abbreviations[i] || ''}`, '');
}

const name = 'Manvendra Singh';

const introduction = abbreviate`Hello, I'm ${name}, and I blog about ${'HTML'}, ${'JS'} and ${'CSS'}!`;
document.querySelector('.bio').innerHTML = introduction;  # Given we have a div with bio class on it.

Here is the output:

Abbreviated output

I think this last example might have shed some light on how important this concept can be. We can use this concept to build custom DSLs (Domain Specific Languages), e.g., we can wrap the DOMPurify library in a Tagging Function and sanitize our strings, much like String.raw does.

Last but not least, ES6 String Literals and Tagging Templates are as cool as other features of the language, like DestructuringGenerators, and Iterators, and the list goes on!

With SDKs for all major client and server side platforms, you can experiment on any platform with Optimizely Full Stack.

Topics:
es6 ,templates ,literals ,coffeescript ,web dev

Published at DZone with permission of Manvendra Singh. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}