© Raju Gandhi 2019
Raju GandhiJavaScript Nexthttps://doi.org/10.1007/978-1-4842-5394-6_5

5. Formatted Strings Using Template Strings

Raju Gandhi
(1)
Columbus, OH, USA
 

Introduction

Ever tried to create a dynamic string in JavaScript that involved interpolating more than one variable at a time? I always seem to mess up the + and the quotes somehow. And heaven forbid we must format it in a particular way with new-lines and indentation. Also, let’s not talk about ever changing that once we get it written out, shall we?
In this chapter we will forever forgo string concatenation in lieu of template strings which allow us to lay out strings that interpolate variables and expressions in a clear and declarative manner. We will also explore tag functions, which can be used to create DSLs in JavaScript. I assure you that by the end of this chapter, you will be itching to refactor every string in your codebase to use template strings. You might even walk away with some ideas on how you can whip up your own DSL!

The Trouble with Forming Formed Strings

JavaScript has the String primitive, and supports constructing strings literally using single quotes or double quotes.1 However, while this works for constructing strings that are static, they prove to be rather cumbersome to work with when building strings programmatically. Consider the following:
const salutation = 'Mr.';
const name = 'Edgar';
const middle = 'Allen';
const last = 'Poe';
const fullName = (salutation ? salutation + ' ' : ") ①
                  + name + ' ' + middle.trim() + ' ' + last; ②
// prints 'Mr. Edgar Allen Poe'
console.log(fullName);
  • ① Ensure we need to put in a salutation if provided
  • ② Compose the full name ensuring no middle name if there isn’t one
As we can see, the intent of what it is we are trying to accomplish is lost as we attempt to distinguish between the “static,” or fixed pieces of the string, and the “dynamic,” or variable pieces of the string. This problem, of course, is further accentuated if the dynamic pieces happen to contain function or method calls, conditionals, or looping constructs.
Many modern JavaScript (and single-page application) frameworks like Angular allow us to embed HTML in the same files as JavaScript (or TypeScript). HTML, like our JavaScript code, is best written and more importantly maintained if it is formatted correctly. Given that our only available option is string concatenation, we are forced to participate in interpolation and indentation machinations2:
const title = 'Section Title'; ①
function HelloComponent() { };
HelloComponent.annotations = [new ng.core.Component({
  selector: 'app-hello-world',
  template: '<article class="content">' + ②
              '<section class="section">' +
                '<div class="col-md-12">' +
                  '<div class="card">' +
                    '<div class="card-block">' +
                      '<div class="card-title-block">' +
                        '<h3 class="title">' + title + '</h3>' + ③
                      '</div>' +
                    '</div>' +
                  '</div>' +
                '</div>' +
              '</section>' +
            '</article>'
})];
  • ① Declare a variable
  • ② Attempt to construct a rather elaborate string using string concatenation
  • ③ Use the variable inline
Good programmers do not let their friends review badly formatted code. Thus, we are not only forced to break up lines using the + operator, we also need to ensure that our code is correctly indented. However, in this case, our editor assumes this to be JavaScript, not realizing that it is actually HTML embedded in JavaScript and will proceed to re-indent all of the code if asked to do so (if it does not do it automatically!). Of course, this concern extends itself to almost any embedded DSL, wherein we have a conflation of interpolated variables, and formatting.
Given what we have, we resort to string concatenation, an archaic interpolation mechanism, all while painstakingly hitting the Enter key at appropriate line lengths in the hope that our linting tool will not fail our build because we left one too many lines badly formatted.
Well, our worries are over. ES6 introduces template literals, and as an extension, tagged template literals.

Template literals

Template literals introduce more syntax, namely the back-tick, or ` as a mechanism to declare strings, and additionally, placeholder syntax, namely ${}, to support interpolation of variables and expressions. Template literals make it easier to compose strings consisting of both literal strings and expressions in-line that are to be evaluated as part of the string creation. Consider the following:
const greeting = 'Hola'; ①
const name = 'Daniel';
const msg = `${greeting}! My name is ${name}`; ②
  • ① Introduce some constant placeholders
  • ② Compose a string using a template literal with variable interpolation
The template literal literally lays out a string, minus the hoops one has to jump through with meticulously placed + operators. Appropriately named variables used in interpolation within the ${} can aid in furthering readability.
The interpolation syntax accepts any expression—be that function calls, math operations, or even ternary expressions.3
Template literals can also be used to lay out multiline strings. If used in this manner, they retain the layout upon evaluation. Given that many modern editors and IDEs already support ES6 syntax, having them auto-format your code means that we no longer lose our layout. Let us see how this were to look if we were to revisit our Angular component example:
const title = 'Section Title'; ①
function HelloComponent() { };
HelloComponent.annotations = [new ng.core.Component({
  selector: 'app-hello-world',
  template: `
    <article class="content"> ②
      <section class="section">
        <div class="col-md-12">
          <div class="card">
            <div class="card-block">
              <div class="card-title-block">
                <h3 class="title">${title}</h3> ③
              </div>
            </div>
          </div>
        </div>
      </section>
    </article>`
})];
  • ① Declare a variable
  • ② Use a template literal to compose a string
  • ③ Interpolate a variable inline
Template literals retain all the formatting we introduce within the back-ticks. In our example our string starts with a newline character, with every line following it indented with two spaces. If we were to simply print it out using console.log, we will see that the template literal remains true to its word. Again, aided by the fact that most editors now support ES6 syntax, it is easy to spot where we are laying out strings literally, as opposed to where we are using the interpolation syntax.
There is one limitation of the interpolation syntax, and that is, we cannot nest interpolations. Let us look at an example:
// for illustrative purposes _ marks a whitespace
{
  const name = 'Edgar';
  const middle = 'Allen';
  const last = 'Poe';
  // prints 'Edgar_Allen_Poe'
  console.log(`${name} ${middle} ${last}`); ①
}
{
  const name = 'Emily';
  const middle = ";
  const last = 'Dickinson';
  // prints 'Emily__Dickinson'
  console.log(`${name} ${middle} ${last}`);
  // prints 'Emily__Dickinson' ②
  console.log(`${name} ${middle.trim()} ${last}`);
  // console.log(`${${name} ${middle}}.trim() ${last}`); ③
  // prints Emily_Dickinson
  console.log(`${`${name} ${middle}`.trim()} ${last}`); ④
}
  • ① Works in an ideal case
  • ② Oops! One too many whitespaces
  • ③ Attempting to nest interpolations will not work
  • ④ We can take advantage of the fact that template literals evaluate to strings
In our ideal case, every candidate has a first, middle, and last name. However, in the case where we do not have a middle name, blindly concatenating strings without a trim in the right places results in one too many spaces. Also, note that our attempt to trim the middle name is also misplaced—trimming the middle name aids in reducing redundant whitespace if the name itself consisted of trailing whitespace. However, it does nothing for the space around an empty middle name.
What we would like to do is concatenate the first and middle name, trim to eliminate any redundant whitespace, and only then tack on the last name.
Since we cannot nest interpolations, we are forced to take a different tack. We can leverage the fact that template literals evaluate to their resultant strings—thus we can nest template strings within template strings—and in a slightly convoluted way, nest interpolations.
While one might object to the readability of nested template strings, and that objection is well justified, it is not uncommon to see it, especially in light of tagged literals, as we will see soon.
Another limitation is the interpolation syntax is ${}, in that, the $ and the {} are required. Many languages like Ruby allow you to skip the {} if simply evaluating a variable, whereas mandate them when evaluating any expression.
What if we wanted to have a back-tick, or a $ sign as a literal? Simple enough. Just escape them using a backslash, as shown here:
const escapingLiterals = `Template strings introduce `` and use ${} for interpolation`;
// prints 'Template strings introduce `` and use ${} for interpolation'
console.log(escapingLiterals);
Template literals solve the problem of composing strings quite elegantly—allowing us to express how a string is to be formed and formatted succinctly. But there is even more good news! ES6 affords us yet another facility that works with template strings, namely tagged literals. Let us look into these next.

Tagged Literals

Tagged literals allow us to use “tags,” or tag functions, in conjunction with template literals, so that we can affect the resulting string and produce more than what we might be able to accomplish with template literals alone.
Let us take a step back and see how template literals work. There are two aspects of a template literal that we can use to change the resultant string—how we concatenate and what we can do with the variables that we interpolate within the template. Consider a situation in which we interpolate string variables within a template string—the manner in which we affect the result depends on what the string object is capable of. For example, we could invoke the toUpperCase method and have a rather effervescent message displayed, like so:
const greeting = 'Hello';
const name = 'Sylvia';
const msg = `${greeting.toUpperCase()}! My name is ${name}!`;
If we were to tease apart the constituent parts of a template string, we see that there are two parts to the equation—the “static” pieces of the string and the “dynamic” pieces of the string. We can envision these as two arrays, like so:
Static vs. dynamic pieces of template strings
const greeting = 'Hello';
const name = 'Sylvia';
const msg = `${greeting}! My name is ${name}!`; ①
//           ___________-------------_______- ②
const fixedStrings = [", '! My name is ', '!']; ③
const values = ['Hello', ' Sylvia']; ④
  • ① A template string interpolating just two variables
  • - denote fixed parts, _ represent dynamic parts
  • ③ The parts of the string that are fixed represented as an array
  • ④ The dynamic, or evaluated pieces of the template string
Just like we did here, we can take any template string, and splice it neatly into two arrays. Furthermore, if we interleaved these two arrays—taking the first item from the fixedStrings array, concatenating it with the first value from the values array, and proceeding till we run out of items—we can reconstitute the template strings ourselves!
Tag functions allow us to do exactly this. They let us intercept the evaluation of the template string by giving us a reference to the constituent parts of the template—the fixed and dynamic pieces, and whatever the return value of the tag function happens to be, is neatly slotted in where we invoked it.
We will start with a simple example, in which we will implement the functionality offered to us by template strings themselves. This will give us an opportunity to understand how to use tag functions, and inspect their signature:
const handler = (strings, ...values) => { ①
  const interleave = (arr1, arr2) =>
    arr1.reduce((arr, v, i) => arr.concat(v, arr2[i]), []);
  return interleave(strings, values).join(");
};
const greeting = 'Hello';
const name = 'Ernest';
const msg = handler`${greeting}! My name is ${name}.`; ②
// prints 'Hello! My name is Ernest.'
console.log(msg);
  • ① Declare a tag function
  • ② Invoke the tag function, handing it a template string
Tag functions have a unique invocation pattern, in that, unlike regular functions that must wrap their arguments in parenthesis, tag functions simply accept a template string in-line. JavaScript splits the template string into its fixed and dynamic parts, passing them into the tag function separately—all of the fixed parts are sent in as the first argument as an array, with the interpolated values sent in consecutively in the same order as they appear in the original template. Thus, in our example, strings is an array of the fixed parts, and we simply capture all of the remaining arguments into an array using the variable arguments syntax.
The rest is simple—simply interleave the two arrays, starting with the first item of the strings array, followed by one from the values array, and proceed till we reach the end of the strings array, and return the result.
If you were to try this out in the console, you will see that you can supply it any template string, and it will return the string just as if were evaluated by JavaScript.
So far, we have implemented something that JavaScript already does for us, so perhaps you are not convinced why tag functions are a good idea. Let us attempt a slightly more elaborate example—there is a very popular extension for many editors called Emmet.4 Once installed, it allows one to write HTML (and CSS) incredibly fast by providing a highly abbreviated syntax that unfolds into valid HTML code. Emmet goes one step further, placing the cursor in the right position after expansion, allowing us the type in the inner HTML. Consider the following examples, where the comments express what a developer would type in their editor that supports Emmet, followed by the Tab key, to see what Emmet would convert the text into (with the _ revealing where the cursor would be placed by Emmet):
<!-- h3.title -> TAB -->
<h3 class="title">_</h3>
<!-- h3.title.content -> TAB -->
<h3 class="title content">_</h3>
<!-- h3.title.content#main -> TAB -->
<h3 class="title content" id="main"></h3>
Emmet syntax is obviously inspired by CSS selectors, such as those that are accepted by document.querySelector and jQuery. Emmet is an interactive medium, in that it can place the cursor at the appropriate spot for the developer to type in the text that represent the innerHTML for the element.
What if we wanted to do something similar programmatically, wherein we could supply Emmet inspired syntax, and our code would expand it into the appropriate HTML? Of course, we need a way to express the innerHTML of the element, since we are going to actually write out all the HTML and don’t have the luxury of appropriately placing a cursor, so we are going to pull a fast one. We will use template literals and tag functions, with one restriction—the innerHTML to the element has to be supplied as an interpolated variable at the end of the template string. In other words, h1.title${someVar} is valid, but h1.title.content${someVar}#main is not. This will let us know which parts compose the HTML element, and what text to place in as the innerHTML.
Finally, given h1.title${someVar} where someVar evaluates to some text the tag function should return <h1 class="title">some text</h1>. Ready?
function ele([strings], ...values) { ①
  const str = strings.trim();
  // assume anything before the FIRST . or # to be the name of the element
  const [element] = str.split(/[.#]/); ②
  // split the remainder of the string into parts using . or # as the delimiter
  // this will grab everything between a '.' or '#' and the next '.' or '#'
  const attrs = str.match(/[.#](?:[^.#]+)/g); ③
  let idStr = ";
  let classStr = ";
  if (attrs) { ④
    // find all ids supplied
    // if multiple ids were supplied just use the last one
    const id = attrs
      .filter(a => a.startsWith('#'))
      .pop(); ⑤
    // do not compose id string if no ids were supplied
    idStr = id
      ? `id="${id.substring(1, id.length)}"` ⑥
      : ";
    // find all classes supplied
    const classes = attrs
      .filter(a => a.startsWith('.'))
      .map(v => v.substring(1, v.length)); ⑦
    // do not compose class string if no classes were supplied
    classStr = (classes.length > 0)
      ? `class="${classes.reduce((acc, v) => `${acc}${v} `, ").trim()}"` ⑧
      : ";
  }
  const adornedElement = [element, idStr, classStr]
    .reduce((acc, v) => `${acc} ${v}`.trim(), "); ⑨
  return `<${adornedElement}>${values.join(")}</${element}>`; ➉
}
const heading = 'Hello Template Handlers';
// prints '<h1>Hello Template Handlers</h1>'
console.log(ele`h1 ${heading}`);
// prints '<h1 id="main">Hello Template Handlers</h1>'
console.log(ele`h1#main ${heading}`);
// prints '<h1 class="title">Hello Template Handlers</h1>'
console.log(ele`h1.title ${heading}`);
// prints '<h1 id="main" class="title content">Hello Template Handlers</h1>'
console.log(ele`h1.title.content#main ${heading}`);
// prints '<h1 id="main" class="title content"><div>Hello Template Handlers</div></h1>'
console.log(ele`h1.title.content#main ${ele`div${heading}`}`);
// prints '<h1 id="main" class="title content"><div class="banner">Hello Template Handlers</div></h1>'
console.log(ele`h1.title.content#main ${ele`div.banner${heading}`}`);
  • ① Our Emmet compliant tag function
  • ② Assume the name of the element to be the first thing in the supplied string
  • ③ Use a regular expression to get all class/Ids markers
  • ④ Nothing to do if it just a plain element with no attributes
  • ⑤ Get the id, and if multiple ids were supplied, take the last one
  • ⑥ Build the id="" string
  • ⑦ Filter for all attributes starting with .
  • ⑧ Build the class="" string
  • ⑨ Build the opening tag string with id/class specified
  • ➉ Write out the full element including its innerHTML and return it
There is a lot going on here, so let us take it one step at a time. Given the constraints we have placed on the usage of our tag functions, we know that the array containing the element description will be a single element array. Hence, we destructure the same to get that one and only element and trim it to eliminate any superfluous whitespace. We also know that the string consists of an element name followed by class (.) and id (#) selectors, so we can use split to get anything before the first appearance of a selector and treat that as the name of the HTML element we are to produce.
Next, some regular expression magic to grab every selector that was supplied after the element name into an array. So, if the user were to supply h1.title.content#main then the regular expression would split this into an array that looks like ['.title', '.content', '#main']. We first filter this array to find all the IDs supplied by the user, and if there were more than one (which is illegal), we simply take the last one supplied, thereby emulating the behavior of Emmet, and compose the id= string. However, multiple classes are allowed, so we filter our array for all the supplied classes and compose the class= string.
The rest is easy—simply bring together the name of the element, the id, and class string, ensuring we are interjecting the innerHTML content (supplied as interpolated variables), and return the fully composed element.
We must highlight a few things about our implementation. First, we use template strings within our function to construct other strings. In so far as a tag function is concerned, other than having an explicit signature, they are just like regular functions. Secondly, as we can see in the examples, we can nest other (tag) function invocations, allowing us to create nested element structures. Since JavaScript has to first evaluate every interpolated expression prior to composing the string, in effect, nested calls to our tag function cause the final string to be built inside out—with the innermost invocation happening first, then moving outward.
Admittedly our implementation is not going to win any awards any time soon. It cannot handle sibling elements (which Emmet supports with the + operator) or creating n number of elements simultaneously (Emmet does this with the operator). However, it does allow us to get some insight into the power of template literals and tag functions, and what they are truly capable of.
Tag functions, along with template strings offer a variety of use-cases, including aiding with internationalization and localization, language translation, and sanitation of user input, where the core essence is transforming a string from one form into another. We could even extend our emmet tag function to emulate behavior exhibited by template languages like JSX5 from React. Libraries like styled-component6 and html-tagged-template7 offer great ideas and incentives for us to consider template literals for many common day-to-day use-cases.
Can you think of any situation where you can eliminate redundancy in your code by introducing such a DSL? How about times where you might be pretty-printing objects for debugging purposes? Or displaying data in a particular format, like locale-specific dates and/or currencies?

Summary

Generating strings that interpolate variables has always been a tedious affair, and template strings aim to alleviate the pain by providing a literal that not only makes it easier, but also has support for multiline strings. Tag functions, on the other hand, allow us to intercept how construction of template strings, allowing for some rather interesting use-cases.
In the next chapter we will switch our attention back to objects and arrays. Objects and arrays have served us well so far, but those of us who come from other languages, we often reminisce about data-structures such as maps and sets, and the lack of such data-structures in JavaScript. That is to be the topic of our next chapter. Alongside we will see how the spread operator and destructuring work hand in hand with these as well.
Footnotes
1
Idiomatically the JavaScript community has leaned toward using single quotes, leaving double quotes for usage scenarios like writing JSON, and situations where we might write HTML in JavaScript. This pattern has found itself codified in popular linters, such as Airbnb’s ( https://github.com/airbnb/javascript/blob/96317f8c79d6c5a6e9478a49eb89770863c4e6e1/README.md#strings%E2%80%94%E2%80%8Bquotes ).
 
2
Angular 6+ strongly enforces the use of TypeScript as the lingua franca. TypeScript is a super-set of JavaScript and supports everything we speak of in this book. However, this example reveals how one would write a component for Angular using plain old ES5 JavaScript. Although this practice is now discouraged, the essence of the problem remains untarnished, thus serving as a good illustration of the limitations of JavaScript strings.
 
3
Recall that an expression always evaluates to something. Statements, such as if conditions, for/while loops, and switch-case, on the other hand, do not evaluate to anything, and thus do not qualify to be used in template literals.
 
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.196.182