Template Literals and Tagged Templates
Template Literals
Greetings, friends! String concatenation sure is annoying, huh?
const firstName = 'Nate'
const lastName = 'Vaughn'
const fullName = `${firstName} ${lastName}` // Ew, string concatenation 🤢
console.log(fullName) // OUTPUT: Nate Vaughn
Wouldn't it be awesome if there was an alternative way that was faster and more readable? Yeah...that would be nice. Good thing Template Literals exist in JavaScript as of the ECMAScript 2015 standard! Let's use a template literal instead!
const firstName = 'Nate'
const lastName = 'Vaughn'
const fullName = `${firstName} ${lastName}` // Wow, template literal! 😃
console.log(fullName) // OUTPUT: Nate Vaughn
So much better! I know what you're thinking. Boring! Not much difference! Well, template literals are a bit more powerful than that. For instance, you can write multi-line strings in a different way.
Instead of this:
console.log('Happy Line 1 🙂\n' + 'Happy Line 2 😊')
/* OUTPUT:
Happy Line 1 🙂
Happy Line 2 😊
*/
You can type the enter/return key to create a new line with template literals:
console.log(`Happy Line 1 🙂
Happy Line 2 😊`)
/* OUTPUT:
Happy Line 1 🙂
Happy Line 2 😊
*/
Template literals can also be nested. Imagine you have classes you need to set on an element, but it's dependent on some variables. Without template literals, you might have something like this:
let classes = 'header'
classes += (isLargeScreen()
? ''
: item.isCollapsed
? ' icon-expander'
: ' icon-collapser')
With template literals and no nesting:
const classes = `header ${isLargeScreen()
? ''
: (item.isCollapsed ? 'icon-expander' : 'icon-collapser')}`
With template literals and nesting:
const classes = `header ${isLargeScreen()
? ''
: `icon-${item.isCollapsed ? 'expander' : 'collapser'}`}`
Another use of template literals is dealing with nested single or double quotes. Suppose you were trying to assign this string to a variable:
const badString = "This is a string with 'single quotes' and "double quotes," but it doesn't work";
You would get a SyntaxError right away. If you use a template literal, then you can easily mix and match single and double quotes without any issues.
const goodString = `This is a string with 'single quotes' and "double quotes," and it works!`
Template literals can be very useful if you're modifying HTML. Suppose we had some HTML like this:
<div id="box"></div>
We could write some JavaScript to insert HTML in the box, but may have to worry about nested quotes:
const box = document.getElementById('box');
box.innerHTML = "<div class="content">I want to write "something" in double quotes and 'something' in single quotes</div>"
But then you get another "SyntaxError". Let's use template literals instead!
const box = document.getElementById('box')
box.innerHTML = `<div class="content">I want to write "something" in double quotes and 'something' in single quotes</div>`
Cool! Syntax errors, begone!!! 😄
Tagged Templates
A more advanced feature of template literals is the concept of tagged templates. They are essentially functions that can parse and operate on the string passed into them. Let's see a simple example.
function template(str, arg1, arg2) {
console.log(`My name is ${arg1}, and I feel ${arg2}!`)
}
const name = 'Nate'
const emotion = 'great'
const greeting = template`${name} ${emotion}`
// OUTPUT: My name is Nate, and I feel great!
What a weird syntax? JavaScript allows that? For tagged templates, yes it does! In the example above, we are writing a template literal, but allowing the tag function, template
, to parse it and perform an operation on it. The first parameter of a tag function contains an array of string values separated by, or rather, delimited by, placeholders such as ${name}
and ${emotion}
. The remaining arguments are the corresponding values represented by the placeholders. Therefore, the following become equivalent in the scope of the invoked template function:
arg1 = name = Nate
arg2 = emotion = Great
Notice what would have happened if I chose not to use the placeholder syntax ${}
function template(str, arg1, arg2) {
console.log(`My name is ${arg1}, and I feel ${arg2}!`)
}
const name = 'Nate'
const emotion = 'great'
const greeting = template`Nate great`
// OUTPUT: My name is undefined, and I feel undefined!
Well, that's kinda awkward. This whole person is undefined! 🙃
The previous example may not seem very useful, but there are some great libraries that utilize tagged templates in a very clever way. The library, graphql-tag, uses tagged templates to help write GraphQL queries. These queries can then be parsed into a generic GraphQL abstract syntax tree (AST) that GraphQL clients can use in any way they want.
import gql from 'graphql-tag'
const query = gql`
{
user(id: 5) {
firstName
lastName
}
}
`
Tagged templates allow developers to create a domain specific language (DSL) that let users only have to worry about writing a string while the library authors deal with the rest. In languages such as Ruby, DSLs are very popular, especially with metaprogramming. There are many ways to write DSLs and many different types of them.
Still need more examples of tagged templates? We can get clever and create an emoji DSL that can use emojis to talk about how we're feeling throughout the week or talk about the weather. Our emoji DSL will let users simply type out a string and then we'll get a weekly dashboard like this:
First, we'll write out part of our tag function and a sample emoji string to get a feeling for it (no pun intended):
function emojiWiz(str, emojis) {
console.log(str)
console.log(emojis)
}
const emojis1 = '😢 😢 😊 😃 😎 😃 😊' // Note the spaces between each emoji
emojiWiz`The week so far:${emojis1}f`
If we run this in the developer console, you'll see the following output (in the Google Chrome browser):
["The week so far:", "f", raw: Array(2)]
😢 😢 😊 😃 😎 😃 😊
Earlier I mentioned that the first parameter in the tag function is a string that is delimited by the placeholders inside the template literal. That is why the first element in the array is "The week so far:" and the second element in the array is "f". The second parameter in the tag function will simply be the value we pass in the first placeholder, which is just our string of emojis. You may notice that there is a raw
property. We won't talk about that much here, but the raw
property lets you access raw strings as they were entered without processing escape sequences. See this MDN article regarding String.raw
for more detail. For now, you should recognize that str
in the above example is actually an array with a property of raw
on it. Remember, in JavaScript, arrays are objects, which means it can have properties, so it's possible to do something like this:
const myArray = [1, 2, 3]
myArray.raw = ['a', 'b']
console.log('Full array: ', myArray)
console.log('Raw: ', myArray.raw)
/* OUTPUT (in Google Chrome):
Full array: [1, 2, 3, raw: Array(2)]
Raw: ["a", "b"]
*/
So now we've recognized that str
is actually an array of two values with a property named raw
. Let's extract out the first two elements and add some lightweight validation logic. Adding validation to make sure only emojis are used is a big task, so we won't go into details of that. This example is already silly, so I don't expect it to be used in a real project 😜.
function emojiWiz(str, emojis) {
const title = str[0]
const type = str[1]
if (!title || !type || !emojis || (type !== 'f' && type !== 'w'))
throw new SyntaxError('Invalid emojiWiz syntax')
}
const emojis1 = '😢 😢 😊 😃 😎 😃 😊'
emojiWiz`The week so far:${emojis1}f`
Next, we will create a small map between emojis and the words they represent. We'll also add a background color to style each day of the week with a color theme that matches the emoji. Feel free to choose any color you like.
const emojiMap = {
'😎': {
text: 'awesome',
backgroundColor: '#FFE26C'
},
'😃': {
text: 'excited',
backgroundColor: '#F6FF6B'
},
'😊': {
text: 'happy',
backgroundColor: '#FF71D9'
},
'😢': {
text: 'sad',
backgroundColor: '#8580FF'
},
'🥵': {
text: 'warm',
backgroundColor: '#FF9C3E'
},
'⛅': {
text: 'cloudy',
backgroundColor: '#FFFEE4'
},
'💧': {
text: 'rainy',
backgroundColor: '#57B1FF'
},
'🥶': {
text: 'chilly',
backgroundColor: '#BCEBFF'
},
}
Then, we'll take the string of emojis we passed into the tag function and split it by spaces to turn it into an array of emojis. It's important that the template literal we pass into emojiWiz
is separated by spaces. If you try to run emojis.split('')
on a template literal that doesn't have a space between each emoji, it will return an array of unicode characters, and that's not what we want.
'😢😢😊😃😎😃😊'.split('') // BAD ❌
'😢 😢 😊 😃 😎 😃 😊'.split(' ') // GOOD ✅
We will create a map between each day of the week and the seven emojis we passed into the template literal.
const emojiArr = emojis.split(' ')
const weeklyEmojis = {
Sunday: emojiArr[0],
Monday: emojiArr[1],
Tuesday: emojiArr[2],
Wednesday: emojiArr[3],
Thursday: emojiArr[4],
Friday: emojiArr[5],
Saturday: emojiArr[6]
}
This is what you should have so far:
function emojiWiz(str, emojis) {
const title = str[0]
const type = str[1]
if (!title || !type || (type !== 'f' && type !== 'w'))
throw new SyntaxError('Invalid emojiWiz syntax')
const emojiMap = {
'😎': {
text: 'awesome',
backgroundColor: '#FFE26C'
},
'😃': {
text: 'excited',
backgroundColor: '#F6FF6B'
},
'😊': {
text: 'happy',
backgroundColor: '#FF71D9'
},
'😢': {
text: 'sad',
backgroundColor: '#8580FF'
},
'🥵': {
text: 'warm',
backgroundColor: '#FF9C3E'
},
'⛅': {
text: 'cloudy',
backgroundColor: '#FFFEE4'
},
'💧': {
text: 'rainy',
backgroundColor: '#57B1FF'
},
'🥶': {
text: 'chilly',
backgroundColor: '#BCEBFF'
},
}
const emojiArr = emojis.split(' ')
const weeklyEmojis = {
Sunday: emojiArr[0],
Monday: emojiArr[1],
Tuesday: emojiArr[2],
Wednesday: emojiArr[3],
Thursday: emojiArr[4],
Friday: emojiArr[5],
Saturday: emojiArr[6]
}
}
const emojis1 = '😢 😢 😊 😃 😎 😃 😊'
emojiWiz`The week so far:${emojis1}f`
Right now, we're not returning anything in the emojiWiz
function or doing anything interesting. This is an emoji language where we want to let the user write out a list of 7 emojis, one for each day of the week, and we, the developers, will take care of all the hard work of displaying a dashboard that represents feelings based on their emojis.
We'll write a bit of HTML and CSS as boilerplate:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Emojiwiz</title>
<style>
body {
text-align: center;
}
#week {
border: 2px solid black;
display: flex;
overflow-y: scroll;
margin: 1rem;
}
#week > .day-container:not(:last-child) {
margin-right: 2rem;
}
.day-container {
flex: 1 0 200px;
width: 200px;
height: 200px;
display: flex;
justify-content: space-around;
align-items: center;
flex-direction: column;
font-size: 2rem;
}
</style>
</head>
<body>
<h1 id="title"><h1>
<div id="week"></div>
<h1 id="type"><h1>
</body>
</html>
Then, we'll loop through an array of each day of the week using Object.keys()
and insert HTML inside the elements based on the emojis passed inside the tagged template.
document.getElementById('title').innerHTML = title
if (type === 'f')
document.getElementById('type').innerHTML = 'Feelings'
if (type === 'w')
document.getElementById('type').innerHTML = 'Weather'
let html = ''
const week = document.getElementById('week')
Object.keys(weeklyEmojis).forEach((day, idx) => {
html += `
<div class="day-container" style="background-color:${emojiMap[weeklyEmojis[day]].backgroundColor}">
<div class="day-name">${day}</div>
<div class="emoji">${weeklyEmojis[day]}</div>
<div class="emoji-text">${emojiMap[weeklyEmojis[day]].text}</div>
</div>
`
})
week.innerHTML = html
When the user types a template literal that adheres to our emoji DSL syntax,
const emojis1 = '😢 😢 😊 😃 😎 😃 😊'
emojiWiz`The week so far:${emojis1}f` // Note the 'f' at the end
They will see a dashboard that summarizes how they felt during each day of the week!
You can add another set of emojis to capture the weather:
const emojis2 = '🥵 🥵 💧 ⛅ ⛅ 🥶 🥶'
emojiWiz`A brand new week:${emojis2}w` // Note the 'w' at the end
Then, the dashboard will instantly change to talk about the weather instead of feelings:
Below is the finished example. You can play around with this example on Codepen if you'd like! Have fun!
function emojiWiz(str, emojis) {
const title = str[0]
const type = str[1]
if (!title || !type || (type !== 'f' && type !== 'w'))
throw new SyntaxError('Invalid emojiWiz syntax')
const emojiMap = {
'😎': {
text: 'awesome',
backgroundColor: '#FFE26C'
},
'😃': {
text: 'excited',
backgroundColor: '#F6FF6B'
},
'😊': {
text: 'happy',
backgroundColor: '#FF71D9'
},
'😢': {
text: 'sad',
backgroundColor: '#8580FF'
},
'🥵': {
text: 'warm',
backgroundColor: '#FF9C3E'
},
'⛅': {
text: 'cloudy',
backgroundColor: '#FFFEE4'
},
'💧': {
text: 'rainy',
backgroundColor: '#57B1FF'
},
'🥶': {
text: 'chilly',
backgroundColor: '#BCEBFF'
},
}
const emojiArr = emojis.split(' ')
const weeklyEmojis = {
Sunday: emojiArr[0],
Monday: emojiArr[1],
Tuesday: emojiArr[2],
Wednesday: emojiArr[3],
Thursday: emojiArr[4],
Friday: emojiArr[5],
Saturday: emojiArr[6]
}
document.getElementById('title').innerHTML = title
if (type === 'f')
document.getElementById('type').innerHTML = 'Feelings'
if (type === 'w')
document.getElementById('type').innerHTML = 'Weather'
let html = ''
const week = document.getElementById('week')
Object.keys(weeklyEmojis).forEach((day, idx) => {
html += `
<div class="day-container" style="background-color:${emojiMap[weeklyEmojis[day]].backgroundColor}">
<div class="day-name">${day}</div>
<div class="emoji">${weeklyEmojis[day]}</div>
<div class="emoji-text">${emojiMap[weeklyEmojis[day]].text}</div>
</div>
`
})
week.innerHTML = html
}
const emojis1 = '😢 😢 😊 😃 😎 😃 😊'
const emojis2 = '🥵 🥵 💧 ⛅ ⛅ 🥶 🥶'
emojiWiz`The week so far:${emojis1}f`
// emojiWiz`A brand new week:${emojis2}w`;
Obviously, this is simple silly example 😜, but hopefully you see how interesting tagged templates can be. Most of the time, you'll be using template literals combined with normal JavaScript functions rather than tagged templates, but tagged templates occassionally have their uses. There are many things we can do to improve the example. We could add extra validation to ensure emojis are the only thing passed to the template literal or let users customize the HTML more.
It's up to you to determine how creative you want to be! Happy coding! :)