Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Template Strings

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

You know what's always kind of a bummer... in any language? Creating strings that have variables inside. I know, that doesn't sound like that big of a deal, but it kind of is... especially in JavaScript!

For example, let's say that our favorite food is, of course, gelato:

5 lines play.js
const favoriteFood = 'gelato';
... lines 2 - 5

And below that, we want to create a string that talks about this: The year is, end quote, plus, and then new Date().getFullYear(), another plus, open quote, space, my favorite food is, another quote, one more plus, and favoriteFood:

5 lines play.js
const favoriteFood = 'gelato';
const iLoveFood = 'The year is '+(new Date()).getFullYear()+' and my favorite food is '+favoriteFood;
... lines 3 - 5

Perfectly straightforward and perfectly ugly! At the bottom, log that:

5 lines play.js
const favoriteFood = 'gelato';
const iLoveFood = 'The year is '+(new Date()).getFullYear()+' and my favorite food is '+favoriteFood;
console.log(iLoveFood);

Now, go enjoy our finished work.

ES2015: Nicer Strings!

This is the way we used to do things in JavaScript. But no more! Thanks to ES2015, we now have something called Template Strings. And it's awesome. Instead of quotes, use a tick. And as soon as you do that, you're allowed to put variables inside of your string! Remove this single quote plus garbage. Replace it with ${, the variable name, then }. Then, remove all the ugliness!

5 lines play.js
const favoriteFood = 'gelato';
const iLoveFood = `The year is ${(new Date()).getFullYear()} and my favorite food is ${favoriteFood}`;
console.log(iLoveFood);

Usually, you'll use this to print a variable, but you can use any expression you want: like we just did with the date.

Try it out! Ah, the same, wonderful string.

Template Strings and Line Breaks

There's something else that makes strings especially annoying in JavaScript: line breaks. In the first episode, we created a client side template and used Underscore.js to render it. We decided to put the template itself into Twig:

... lines 1 - 53
{% block javascripts %}
... lines 55 - 66
<script type="text/template" id="js-rep-log-row-template">
<tr data-weight="<%= totalWeightLifted %>">
<td><%= itemLabel %></td>
<td><%= reps %></td>
<td><%= totalWeightLifted %></td>
<td>
<a href="#"
class="js-delete-rep-log"
data-url="<%= links._self %>"
>
<span class="fa fa-trash"></span>
</a>
</td>
</tr>
</script>
{% endblock %}

Then, in _addRow(), we found that script element by its id and fetched the string:

... lines 1 - 2
(function(window, $, Routing, swal) {
class RepLogApp {
... lines 5 - 166
_addRow(repLog) {
... lines 168 - 171
const tplText = $('#js-rep-log-row-template').html();
const tpl = _.template(tplText);
const html = tpl(repLog);
this.$wrapper.find('tbody').append($.parseHTML(html));
this.updateTotalWeightLifted();
}
}
... lines 181 - 216
})(window, jQuery, Routing, swal);

So, why did we put the the template in Twig? Well, it's not a bad option, but mostly, we did it because putting that template into JavaScript was, basically, impossible.

Why? Try it! Copy the template, find the bottom of RepLogApp.js, create a new const rowTemplate and paste the string inside single quotes:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 215
const rowTemplate = '
<tr data-weight="<%= totalWeightLifted %>">
<td><%= itemLabel %></td>
<td><%= reps %></td>
<td><%= totalWeightLifted %></td>
<td>
<a href="#"
class="js-delete-rep-log"
data-url="<%= links._self %>"
>
<span class="fa fa-trash"></span>
</a>
</td>
</tr>
';
... lines 231 - 232
})(window, jQuery, Routing, swal);

Oh wow, PHP storm is SO angry with me. Furious! And that's because, unlike PHP, you are not allowed to have line breaks inside traditional strings. And that makes putting a template inside of JavaScript basically impossible.

Guess what? Template strings fix that! Just change those quotes to ticks, and everything is happy:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 216
const rowTemplate = `
<tr data-weight="<%= totalWeightLifted %>">
<td><%= itemLabel %></td>
<td><%= reps %></td>
<td><%= totalWeightLifted %></td>
<td>
<a href="#"
class="js-delete-rep-log"
data-url="<%= links._self %>"
>
<span class="fa fa-trash"></span>
</a>
</td>
</tr>
`;
... lines 232 - 233
})(window, jQuery, Routing, swal);

To celebrate, remove the template from Twig:

... lines 1 - 53
{% block javascripts %}
{{ parent() }}
<script src="https://cdn.jsdelivr.net/sweetalert2/6.1.0/sweetalert2.min.js"></script>
<script src="{{ asset('assets/js/RepLogApp.js') }}"></script>
<script>
$(document).ready(function() {
var $wrapper = $('.js-rep-log-table');
var repLogApp = new RepLogApp($wrapper);
});
</script>
{% endblock %}

Now, we can simplify things! Up in _addRow(), set tplText to simply equal rowTemplate:

... lines 1 - 2
(function(window, $, Routing, swal) {
class RepLogApp {
... lines 6 - 167
_addRow(repLog) {
... lines 169 - 172
const tplText = rowTemplate;
... lines 174 - 179
}
}
... lines 182 - 216
const rowTemplate = `
<tr data-weight="<%= totalWeightLifted %>">
<td><%= itemLabel %></td>
<td><%= reps %></td>
<td><%= totalWeightLifted %></td>
<td>
<a href="#"
class="js-delete-rep-log"
data-url="<%= links._self %>"
>
<span class="fa fa-trash"></span>
</a>
</td>
</tr>
`;
window.RepLogApp = RepLogApp;
})(window, jQuery, Routing, swal);

Using Template Strings as a Template Engine

But let's go a step further! Now that we have this nice ability to embed variables into our strings, could we create our own templating engine, instead of using the one from Underscore? I mean, there's nothing wrong with Underscore, but we can accomplish this with pure template strings.

First, update the template with ${repLog.totalWeightLifted}. In a moment, instead of making the individual variables available, we'll just set one variable: the repLog object:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 216
const rowTemplate = `
<tr data-weight="${repLog.totalWeightLifted}">
... lines 219 - 229
</tr>
`;
... lines 232 - 233
})(window, jQuery, Routing, swal);

Of course, PhpStorm is angry because there is no repLog variable... but we can fix that!

Next, make the same change very carefully to the other variables: replacing the kind of ugly syntax with the template string syntax. And yea, be more careful than I am: I keep adding extra characters!

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 216
const rowTemplate = `
<tr data-weight="${repLog.totalWeightLifted}">
<td>${repLog.itemLabel}</td>
<td>${repLog.reps}</td>
<td>${repLog.totalWeightLifted}</td>
<td>
<a href="#"
class="js-delete-rep-log"
data-url="${repLog.links._self}"
>
<span class="fa fa-trash"></span>
</a>
</td>
</tr>
`;
... lines 232 - 233
})(window, jQuery, Routing, swal);

At this point, this is a valid template string... with one screaming problem: there is no repLog variable! If we refresh now, we of course see:

repLog is not defined

Turning our Template into a Function

Here's the goal: inside of _addRow(), we need to somehow use that template string, and pass it the repLog variable we have here. But... how can we make this variable available... way down here?

Hey! Here's an idea: let's wrap this in a function! Instead of rowTemplate simply being a string, set it to an arrow function with a repLog argument:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 213
const rowTemplate = (repLog) => `
<tr data-weight="${repLog.totalWeightLifted}">
<td>${repLog.itemLabel}</td>
<td>${repLog.reps}</td>
<td>${repLog.totalWeightLifted}</td>
<td>
<a href="#"
class="js-delete-rep-log"
data-url="${repLog.links._self}"
>
<span class="fa fa-trash"></span>
</a>
</td>
</tr>
`;
... lines 229 - 230
})(window, jQuery, Routing, swal);

And suddenly, the template string will have access to a repLog variable.

Back in _addRow(), remove all this stuff and very simply say html = rowTemplate(repLog):

... lines 1 - 2
(function(window, $, Routing, swal) {
class RepLogApp {
... lines 6 - 167
_addRow(repLog) {
... lines 169 - 172
const html = rowTemplate(repLog);
this.$wrapper.find('tbody').append($.parseHTML(html));
this.updateTotalWeightLifted();
}
}
... lines 179 - 230
})(window, jQuery, Routing, swal);

And that is it! Try that out! Refresh! The fact that it even loaded proves it works: all of these rows are built from that template.

Tagged Template Strings

Before we move on, there's one other strange thing you might see with template strings.

If you downloaded the start code for this project, you should have a tutorial directory with an upper.js file inside. Copy that function. And then, at the bottom of RepLogApp.js, paste it before the template:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 213
function upper(template, ...expressions) {
return template.reduce((accumulator, part, i) => {
return accumulator + (expressions[i - 1].toUpperCase ? expressions[i - 1].toUpperCase() : expressions[i - 1]) + part
})
}
... lines 219 - 236
})(window, jQuery, Routing, swal);

Just follow me on this: right before the tick that starts the template string, add the function's name: upper:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 213
function upper(template, ...expressions) {
return template.reduce((accumulator, part, i) => {
return accumulator + (expressions[i - 1].toUpperCase ? expressions[i - 1].toUpperCase() : expressions[i - 1]) + part
})
}
const rowTemplate = (repLog) => upper`
... lines 221 - 233
`;
... lines 235 - 236
})(window, jQuery, Routing, swal);

So literally upper, then without any spaces, the opening tick. This is called a tagged template. And by doing this, the string and its embedded expressions will be passed into that function, allowing it to do some transformations. In this case, it'll upper case all the words.

Other possible uses for tagged templates might be to escape variables. But honestly, the web seems to be filled mostly with possible use-cases, without too many real-world examples. And also, these functions are really complex to write and even harder to read!

Oh, btw, the upper function uses the spread operator in a different way: to allow the function to have a variable number of arguments!

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 213
function upper(template, ...expressions) {
... lines 215 - 217
}
... lines 219 - 236
})(window, jQuery, Routing, swal);

Anyways, let's see if it works! Refresh! Hey, all uppercase! Ok, kinda cool! You may not use tagged template strings, but you might see them. And now, you'll understand what the heck is going on!

Remove the upper function and put everything back to normal:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 213
const rowTemplate = (repLog) => `
... lines 215 - 227
`;
... lines 229 - 230
})(window, jQuery, Routing, swal);

Leave a comment!

2
Login or Register to join the conversation
Agnes Avatar

Hmm, no tutorial directory in the code.

Reply

Ohh, that was totally a mistake, thanks Agnes for letting us know!
We really apologize if caused you any problem, it's already fixed, you can download the project and just copy it into your working project :)

If you have more questions or comments let us know.
Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses Symfony 3. But, since this is a JavaScript tutorial, all the concepts work fine in newer versions of Symfony.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "symfony/symfony": "3.2.*", // v3.2.14
        "twig/twig": "2.10.*", // v2.10.0
        "doctrine/orm": "^2.5", // v2.7.1
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
        "symfony/swiftmailer-bundle": "^2.3", // v2.4.2
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.3.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.19
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "~2.0@dev", // dev-master
        "doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.2
        "symfony/phpunit-bridge": "^3.0" // v3.2.2
    }
}
userVoice