Writing HTML in JavaScript made easy with template literals

OpenJavaScript 0
Reading Time: 5 minutes πŸ•‘

Last updated: March 24, 2022.

Template literals (alternatively ‘template strings’) are a game-changer for writing HTML in JavaScript.

With a template literal, you can write an entire blocks of multi-line HTML as you would in a HTML file. And you can easily insert dynamic content from using JavaScript into the template!

The template can then be rendered to the DOM using the innerHTML method.

Writing HTML with a template literal

You may wonder what’s so special about template literals. After all, string literals can be rendered to the DOM as HTML:

const markup = "<h1>Hello from a string literal</h1> πŸ‘‹"

document.body.innerHTML = markup;

But if you have experience with string literals, you will know that this can get messy quickly, especially if the string is long and there is a lot of dynamic content insertion.

Template literals make life much easier: between backticks (``), you can write HTML as you would normally in a HTML file. And if you want to insert content dynamically, this can be placed between the special ${} syntax. Content inserted in this way is known as a substitute.

For example, here is a multi-line template literal with substitutions from the user object:

const user = {
    name: 'Peter Parker',
    job: 'πŸ•ΈοΈ',
    city: 'New York'
}

// Create the HTML:
const myHTML = `
 <div class="user-bio">
    <h2>
        ${user.name}
    </h2>
    <p class="bio-text">${user.name} lives in ${user.city} and works as ${user.job}</p>
 </div>
`;

document.body.innerHTML = myHTML;

And here is its equivalent written using a string literal:

const myStringLiteral = "<div class=\"user-bio\"> \n <h2> \n "+user.name+" </h2> \n <p class=\"bio-text\">"+user.name+" lives in "+user.city+" and works as "+user.job+"</p> \n </div>";
// 😱

That's really tricky to decipher!

Here are some advantages to writing blocks of HTML with template literals:

  • Multi-line without the need for special characters
  • No annoying single- or double-quotation mark escapes!
  • Dynamic content insertion with ${}
  • Familiar, HTML-like syntax

Dynamic content interpolation

In the example above, string data was inserted dynamically in ${} syntax.

The process of converting substitutions to strings values is known as string interpolation. It allows any singular expression that returns a value to be interpolated.

For example:

const numbers = [9,47];

const myTemplateLiteral = `The sum ${numbers[0]} times ${numbers[1]} equals ${numbers[0]*numbers[1]}`;

console.log(myTemplateLiteral);
// The sum 9 times 47 equals 423

When writing substitutions, you are effectively switching from writing HTML to JavaScript-land for a brief moment, and have the full flexibility of a programming language available to you.

Interpolating function calls

You might sometimes was to do more than is possible in a single expression. For this, you can call a function inside ${} (because the call is a single expression) and the return value of the function will be interpolated. This allows you to do something more complex than is possible in a single statement.

For example, in the code below, the formatKeywords function is called as a substitution. What is interpolated is the return value of the function (in this case, a formatted version of the keywords):

const article = {
    title: "Learning JavaScript template literals",
    category: "Modern JavaScript",
    body: "Using template literals makes writing HTML in JavaScript much easier.",
    keywords: ["JavaScript", "HTML", "markup", "strings", "interpolation"]
}

function formatKeywords(keywords) {
    const formatted = keywords.map((keyword) => {
        return "#"+(keyword.toLowerCase())+"";
    })
    const joined = formatted.join(', ');
    return joined;
}

const markup = `
    <article>
       <div>
            <h1>${article.title}</h1>
            <h2>${article.category}</h2>
        </div>
        <div>
            <p>${article.body}</p>
        </div>
        <div>
            <span>${formatKeywords(article.keywords)}</span>
        </div>
    </article>
`

document.body.innerHTML = markup;

Iterating literals within a template literal

Sometimes it can be useful to iterate string literals inside a string literal.

You can do this using the map method:

const yumYum = ["πŸ” Burger","πŸ₯— Salad","🍣 Sushi","πŸ₯§ Pie", "🍚 Rice dish"]

const markup = `
<ul>
    ${yumYum.map(item => `<li>${item}</li>`).join('')}
</ul>
`

document.body.innerHTML = markup;

The same would not be possible with a loop because it is not a singular expression.

Optional template tagging

A nice additional feature of template literals is that it is possible to pass them through a reusable tagging function before rendering them to the DOM.

For example, the following template literal interpolates some user data from the user object. This contains some code that, if rendered to the DOM, would run user-generated JavaScript:

const user = {
    name: 'Joe',
    bio: `I love to write JavaScript and wouldn't dream of including a malicious script here 😈 <br> <img onload="console.log('User-run JavaScript!')" src="https://unsplash.it/200/200">`,
}

const markup = `
<div>
    <h1>${user.name}</h1>
    <p>${user.bio}</p>
</div>`

document.body.innerHTML = markup;

We can fix this by running our template literal through a reusable tagging function that, in this case, will sanitize the HTML. For this, we will use the HTML sanitizer DOMPurify.

We pass the template literal through a tagging function by calling the function before the literal without parentheses. Doing this invokes the tagging function. The string parts of the template literal are passed in as an array and each substitution as a separate argument. But inside the tagging function, both are available as arrays.

So in our example, the values of the strings are:

"\n<div>\n    <h1>", "</h1>\n    <p>", "</p>\n</div>"

And the substitutions are:

"Joe", "I love to write JavaScript and wouldn't dream of including a malicious script here 😈 <br> <img onload=\"alert('User-run JavaScript!')\" src=\"https://unsplash.it/200/200\">"

To sanitize the user input, we run it through the DOMPurify.sanitize() method when reconstituting the template literal inside the function.

The return value of the tagging function is what is now stored in the markup variable and rendered to the DOM.

// Assumes DOMPurify was loaded in an earlier script!

function sanitize(strings, ...substitutions) {
    let result = '';
    for (let i = 0; i < substitutions.length; i++) {
        result += strings[i];
        result += DOMPurify.sanitize(substitutions[i]);
    }
    // add the last literal
    result += strings[strings.length - 1];
    return result;
}

const user = {
    name: 'Joe',
    bio: `I love to write JavaScript and wouldn't dream of including a malicious script here 😈 <br> <img onload="alert('User-run JavaScript!')" src="https://unsplash.it/200/200">`,
}

const markup = sanitize`
<div>
    <h1>${user.name}</h1>
    <p>${user.bio}</p>
</div>`

document.body.innerHTML = markup;

Because the user input has been run through DOMPurify, the user-generated script no longer runs! And the tagging function is reusable, so we can pass more template literals through it.

Summary

Template literals, otherwise known as template strings, are a breakthrough in rendering HTML to the DOM from JavaScript: just place the content between backticks and use the special ${} syntax to insert dynamic content (a substitution).

You can even process template literals through reusable tagging functions before rendering them to the DOM to sanitize any potentially malicious substitutions or provide formatting.