Utilize a repository of reusable ES6 template literals

The Template Literals introduced with ES6 are very useful to deal with multiline strings, because they support embedded expressions. Gone are the days of endless string concatination or replacing variables in a string by using RegEx.

Instead of…

1
2
3
4
5
6
7
8
var url = ...
var file = ...

var template =
'<div class="photo">' +
'<a href="' + url + "' +
'style="background-image: url(' + file + ')"</a>' +
'</div>'

… you can write:

1
2
3
4
5
6
7
8
9
var url = ...
var file = ...

var template = `
<div class="photo">
<a href="${url}/"
style="background-image: url(${file});"></a>
</div>
`,

It’s much cleaner and easier to handle, as you can copy your needed HTML right into your code and surround it by backtick (!) characters. Insert your variable placeholders (expressions), indicated by a dollar sign and curly braces, and you are done.

But there is one “restriction”, you have to be aware of: the interpolation (substitution of the expressions) is done at declaration time and not at runtime. You can’t define your literals seperatly, take one and make your substitution as you need it, like you would do with Handlebars or other templating engines. Therefore the name template literals is a bit misleading. But … there is a way to achieve this anyway…

Tagged Templates

Beside Template Literals, ES6 introduced Tagged Templates (exact: Tagged Template Literals). These tags are functions, which allows you to parse a Template Literal. Definition is like this:

1
2
3
function myTag(literals, ...expressions) {
//do the substitution and return a string
}

You can use these tags by prefixing you literal:

1
myTag`Hello ${firstName} ${lastName}!`

Using Tagged Templates to build a template repository would mean, you have to write one tag function for every template … doable, but time consuming.

Dynamic Tag Function

To avoid this, we can write a universal tag function, which utilizes the Function constructor, to create the tag function dynamically:

1
2
3
4
5
6
7
8
function fillTemplate(templateString, templateVars) {

var func = new Function(
...Object.keys(templateVars),
"return `" + templateString + "`;")

return func(...Object.values(templateVars));
}

Don’t use this approach on user inputs as expressions, to avoid XSS!

Let’s see an example…

Given is a tiny web app with the following structure:

index.html
1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<title>Reusable ES6 template literals</title>
<meta charset="UTF-8" />
<link rel="stylesheet" href="/src/style.css">
</head>
<body>
<main id="main"></main>
<script src="src/index.js"></script>
</body>
</html>
index.js
1
2
3
4
5
import { App } from "./app.js";

const app = new App();

app.init();
app.js
1
2
3
4
5
6
class App {
init() {
//do something
}
}
export { App };

What we want to do now, is to load some images into the main element, by using a more or less complex element structure:

1
2
3
4
<div class="photo">
<a href="<!-- Url to view the photo -->"
style="background-image: url(<!-- Url of the photo file -->);"></a>
</div>

To separate our templates from the main code, we create a template module, which contains the dynamic tag function from above and a photo template we want to use in our app

template.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Templates {

//Template
photo(data) {
return this.fillTemplate(
`
<div class="photo">
<a href="${data.url}/"
style="background-image: url(${data.file});"></a>
</div>
`,
data
);
}

//Dynamic Tag Function
fillTemplate(templateString, templateVars) {
var func = new Function(...Object.keys(templateVars),
"return `" + templateString + "`;"
);
return func(...Object.values(templateVars));
}

}
export { Templates };

The template retrieves a data object, with the values of the defined expressions, and calls the dynamic tag function on the literal template.

This we can use now in our app code:

app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//Import Template module
import { Templates } from "./templates.js";

class App {
init() {

//Initialize Templates
this._templates = new Templates();

//Insert photo into MAIN element
let main = document.getElementById("main");
main.insertAdjacentHTML(
"beforeend",
this._templates.photo({
file: "my-photo.jpg",
url: "https://link-to-my.photo.com"
})
);

}
}
export { App };

See it live at codesandbox.io.

More Info

Related