Advanced String Manipulation with Tagged Templates In JavaScript
Updated on · 5 min read|Template literals are a feature in JavaScript that allows for easier string creation and manipulation. Tagged templates provide an advanced and flexible approach that goes beyond the capabilities of regular template literals. Tagged templates enable processing template literals using custom functions, giving you fine-grained control over how strings and embedded expressions are concatenated and manipulated.
In this blog post, we will explore the basics of template literals and various use cases where they prove to be particularly useful. Then we'll dive into a more advanced form called tagged templates, which provide even greater control over string construction. By the end of this post, you'll have a solid understanding of how to leverage tagged templates for advanced string manipulation in your JavaScript code.
Basics of Template Literals
Before talking about the tagged templates, it’s important to understand the foundation, which is template literals. Template literals are a feature in JavaScript that allows for easier string creation and manipulation. They were introduced in ECMAScript 6 (ES6) and have since become a staple in modern JavaScript development.
Unlike traditional strings which can be defined using single quotes (') or double quotes ("), template literals are defined using backticks (`). Inside these backticks, you can write multi-line strings and embed expressions that will be evaluated and concatenated into the string.
javascriptconst greeting = `Hello, World!`;
javascriptconst greeting = `Hello, World!`;
One of the major advantages of using template literals is the ease of creating multi-line strings. You do not need to use escape characters or concatenate strings with +
. Simply use backticks and write your string across multiple lines.
javascriptconst poem = `Roses are red, Violets are blue, JavaScript is awesome, And so are you!`; console.log(poem);
javascriptconst poem = `Roses are red, Violets are blue, JavaScript is awesome, And so are you!`; console.log(poem);
Another powerful feature is string interpolation. You can embed any valid JavaScript expression inside a template literal using ${expression}
syntax. This expression will be evaluated, and its result will be concatenated into the string.
javascriptconst name = "John"; const age = 30; const message = `My name is ${name} and I am ${age} years old.`; console.log(message);
javascriptconst name = "John"; const age = 30; const message = `My name is ${name} and I am ${age} years old.`; console.log(message);
If you're curious about using template literals in TypeScript, you may find this post helpful: TypeScript Template Literal Types: Practical Use-Cases for Improved Code Quality.
Use cases for Template Literals
Template literals can be used in various scenarios, such as:
- Dynamic Strings: Easily create strings that change based on variables or conditions.
- Multi-line Strings: Write cleaner code by avoiding concatenation for multi-line strings.
- HTML Templates: Generate HTML strings dynamically, particularly useful in frameworks like React or Angular.
- String Formatting: Create custom string formats easily by embedding expressions within the template literals.
The ability to embed expressions and create multi-line strings with ease makes template literals a powerful tool for string manipulation in JavaScript.
Understanding tagged templates
Tagged templates are a more advanced form of template literals. They allow you to use a function to process the template literal, rather than just evaluating it as a string. This provides you with more control over how the strings and embedded expressions are concatenated.
The syntax of a tagged template is similar to calling a function with an argument, but instead of using parentheses, you use a template literal. The function that is used to tag the template is called a "tag function".
javascripttagFunction`string text ${expression} string text`;
javascripttagFunction`string text ${expression} string text`;
The tag function receives two sets of parameters:
- An array of string values (the text between the expressions).
- The results of evaluating the expressions.
javascriptfunction myTag(strings, ...values) { console.log(strings); console.log(values); } let name = "John"; myTag`Hello ${name}`;
javascriptfunction myTag(strings, ...values) { console.log(strings); console.log(values); } let name = "John"; myTag`Hello ${name}`;
In this example, strings would be an array ["Hello ", ""]
, and values would be an array ["John"]
.
Differences between template literals and tagged templates
It’s important to distinguish between regular template literals and tagged templates:
-
Template Literals simply evaluate to a string, where expressions are inserted directly into the string.
-
Tagged Templates pass the literal strings and the results of the expressions to a function, which can then manipulate and return them in a custom way.
Tagged templates offer more flexibility and control over the string construction process, making them powerful tools for advanced string manipulation.
Advanced string manipulation using tagged templates
Tagged templates open up a plethora of possibilities for advanced string manipulation. Let's explore some of the practical applications that can leverage tagged templates for custom string formatting, HTML character escaping, localized string creation, and complex string composition.
Custom string formatting
Tagged templates can be used for creating custom formats for strings, such as dates and numbers.
javascriptfunction formatDate(strings, ...values) { const outputArray = values.map( (value, index) => `${strings[index]}${value instanceof Date ? value.toLocaleDateString() : value}`, ); return outputArray.join("") + strings[strings.length - 1]; } const myDate = new Date(); console.log(formatDate`Today's date is ${myDate}.`); // Today's date is 12/06/2023.
javascriptfunction formatDate(strings, ...values) { const outputArray = values.map( (value, index) => `${strings[index]}${value instanceof Date ? value.toLocaleDateString() : value}`, ); return outputArray.join("") + strings[strings.length - 1]; } const myDate = new Date(); console.log(formatDate`Today's date is ${myDate}.`); // Today's date is 12/06/2023.
The formatDate
function takes in two parameters: strings
and values
. The function iterates over the values array and concatenates the corresponding values with the respective strings from the strings array. If a value is a Date
object, it appends the string with the formatted date obtained using the toLocaleDateString()
method. If the value is not a Date
object, it simply appends the value itself. The function returns the concatenated string, including the last part of the template literal.
Another example is to add some custom formatting to numbers.
javascriptfunction formatNumber(strings, ...values) { const outputArray = values.map( (value, index) => `${strings[index]}${typeof value === "number" ? value.toLocaleString() : value}`, ); return outputArray.join("") + strings[strings.length - 1]; } const myNumber = 1000000; console.log(formatNumber`The number is ${myNumber}.`); // The number is 1,000,000.
javascriptfunction formatNumber(strings, ...values) { const outputArray = values.map( (value, index) => `${strings[index]}${typeof value === "number" ? value.toLocaleString() : value}`, ); return outputArray.join("") + strings[strings.length - 1]; } const myNumber = 1000000; console.log(formatNumber`The number is ${myNumber}.`); // The number is 1,000,000.
Escaping HTML characters
Escaping HTML characters is crucial for preventing Cross-Site Scripting (XSS) attacks. Tagged templates can be utilized for this purpose.
javascriptfunction escapeHtml(strings, ...values) { const escaped = values.map((value) => { return String(value) .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }); return strings.reduce( (result, string, i) => `${result}${string}${escaped[i] || ""}`, "", ); } let userInput = '<img src="x" onerror="alert(\'XSS\')">'; // Potentially harmful user input let safeHtml = escapeHtml`<div>${userInput}</div>`; console.log(safeHtml); // Outputs: <div><img src="x" onerror="alert('XSS')"></div>
javascriptfunction escapeHtml(strings, ...values) { const escaped = values.map((value) => { return String(value) .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }); return strings.reduce( (result, string, i) => `${result}${string}${escaped[i] || ""}`, "", ); } let userInput = '<img src="x" onerror="alert(\'XSS\')">'; // Potentially harmful user input let safeHtml = escapeHtml`<div>${userInput}</div>`; console.log(safeHtml); // Outputs: <div><img src="x" onerror="alert('XSS')"></div>
The escapeHTML
function is used to safely escape HTML characters in the userInput
string. The string is passed as an expression within the tagged template literal. The escapeHTML
function processes the template literal, escapes the HTML characters in the userInput
, and returns the fully escaped string.
Localized string creation
Tagged templates can be used to dynamically create localized strings based on the user’s language preference, enabling multi-language support.
javascriptconst i18n = { en: { hello: "Hello, %s!", }, es: { hello: "¡Hola, %s!", }, }; function localize(lang) { return function (strings, ...values) { const string = strings[0]; const localized = i18n[lang][string]; return localized.replace("%s", values[0]); }; } const name = "John"; const helloTemplate = localize("es")`hello ${name}`; console.log(helloTemplate); // ¡Hola, John!
javascriptconst i18n = { en: { hello: "Hello, %s!", }, es: { hello: "¡Hola, %s!", }, }; function localize(lang) { return function (strings, ...values) { const string = strings[0]; const localized = i18n[lang][string]; return localized.replace("%s", values[0]); }; } const name = "John"; const helloTemplate = localize("es")`hello ${name}`; console.log(helloTemplate); // ¡Hola, John!
Complex string composition
Tagged templates can be utilized for generating complex strings, such as Markdown or other structured text formats.
javascriptfunction markdown(strings, ...values) { const outputArray = strings.map((string, i) => { return string + (values[i] ? `**${values[i]}**` : ""); }); return outputArray.join(""); } const title = "Tagged Templates"; const author = "John Doe"; console.log( markdown`# ${title}\nAuthor: ${author}\nThis is a blog post about tagged templates.`, ); // Output: // # **Tagged Templates** // Author: **John Doe** // This is a blog post about tagged templates.
javascriptfunction markdown(strings, ...values) { const outputArray = strings.map((string, i) => { return string + (values[i] ? `**${values[i]}**` : ""); }); return outputArray.join(""); } const title = "Tagged Templates"; const author = "John Doe"; console.log( markdown`# ${title}\nAuthor: ${author}\nThis is a blog post about tagged templates.`, ); // Output: // # **Tagged Templates** // Author: **John Doe** // This is a blog post about tagged templates.
Another popular example is using tagged templates to safely build SQL queries.
javascriptfunction sql(strings, ...values) { const queryArray = strings.map((string, i) => { return string + (i < values.length ? `'${values[i]}'` : ""); }); return queryArray.join(""); } const tableName = "users"; const userId = "123"; const query = sql`SELECT * FROM ${tableName} WHERE id = ${userId}`; console.log(query); // Outputs: SELECT * FROM 'users' WHERE id = '123'
javascriptfunction sql(strings, ...values) { const queryArray = strings.map((string, i) => { return string + (i < values.length ? `'${values[i]}'` : ""); }); return queryArray.join(""); } const tableName = "users"; const userId = "123"; const query = sql`SELECT * FROM ${tableName} WHERE id = ${userId}`; console.log(query); // Outputs: SELECT * FROM 'users' WHERE id = '123'
Tagged templates provide fine-grained control over string construction, making them a powerful tool for advanced string manipulation. From custom formatting to security and localization, their applications are wide and varied.
Conclusion
Template literals are a valuable addition to JavaScript, offering a concise and powerful way to manipulate strings. By using backticks instead of quotes, you can easily create multi-line strings and embed expressions for dynamic content. This feature simplifies string manipulation tasks, such as creating dynamic strings, generating HTML templates, and formatting strings. Furthermore, tagged templates take string manipulation to the next level, allowing you to customize the string construction process by using tag functions. With tagged templates, you can create custom string formats, escape HTML characters, generate localized strings, and compose complex structured texts. By mastering template literals and tagged templates, you'll have a powerful set of tools to handle various string manipulation challenges in JavaScript.