Tagged Template Literals

Tagged template literals let you direct the output of a template literal using a function:

function myTag(strings, firstName, lastName) {
  return strings[0] +
    firstName +
    strings[1] +
    lastName;
}

let first = 'Davy';
let last = 'Crockett';

console.log(myTag`First Name: ${first}, Last Name: ${last}`);

// prints "First Name: Davy, Last Name: Crockett"

Another way of thinking about this: untagged template strings do string concatenation, and tagged template strings do something else. That something else is defined by the function you use as a tag. Your something else may include string concatenation. It doesn’t have to, though.

The first argument passed to the tag function will be an array of the string parts of the template literal. The subsequent arguments will be each of the evaluated expressions. In the above case, strings holds all the strings, firstName holds the value that comes from ${first}, and lastName holds the value that comes from ${last}.

Let’s get fancy. Here’s a tag function that does the same thing as an untagged template:

function concatTag(strings, ...values) {
  let result = '';

  for (let i = 0; i < strings.length; i++) {
    result += strings[i];

    if (values[i] !== undefined) {
      result += values[i];
    }
  }

  return result;
}

let first = 'Davy';
let last = 'Crockett';

console.log(concatTag`First Name: ${first}, Last Name: ${last}!`);

// prints "First Name: Davy, Last Name: Crockett!"

Note that we’ve used the rest parameter ...values in this new tag. This gives us a convenient array of all the expression values passed into the tag function so we can handle any number of them.

We can thus loop through all the strings and values to construct our result.

Now let’s get a little more practical. Let’s write a tag that actually does something useful. What if I want to make sure my dynamic values are HTML-escaped?

function escapeHtml(target) {
  return target.replace(/</g, '&lt;')
    .replace(/>/g, "&gt;");
}

function htmlEscapeTag(strings, ...values) {
  let result = '';

  for (let i = 0; i < strings.length; i++) {
    result += strings[i];
    if (values[i] !== undefined) {
      result += escapeHtml(values[i]);
    }
  }

  return result;
}

let first = '<span>Davy</span>';
let last = '<span>Crockett</span>';

console.log(htmlEscapeTag`First Name: ${first}, Last Name: ${last}`);

// prints "First Name: &lt;span&gt;Davy&lt;/span&gt;, Last Name: &lt;span&gt;Crockett&lt;/span&gt;" Nice.