JavaScript Template Literals: Modern String Handling
Master template literals in JavaScript for string interpolation, multi-line strings, and tagged templates. Learn advanced techniques and best practices.
JavaScript Template Literals: Modern String Handling
Template literals (template strings) revolutionized string handling in JavaScript. They provide an elegant way to create strings with embedded expressions, multi-line support, and advanced formatting capabilities.
Basic Template Literals
Template literals are enclosed by backticks (` `) instead of quotes.
// Traditional strings
const single = 'Hello World';
const double = 'Hello World';
// Template literal
const template = `Hello World`;
// Key differences
const name = 'John';
// Old way - concatenation
const greeting1 = 'Hello, ' + name + '!';
// Template literal - interpolation
const greeting2 = `Hello, ${name}!`;
console.log(greeting2); // "Hello, John!"
String Interpolation
Basic Interpolation
// Variables
const firstName = 'John';
const lastName = 'Doe';
const age = 30;
const bio = `My name is ${firstName} ${lastName} and I am ${age} years old.`;
console.log(bio);
// "My name is John Doe and I am 30 years old."
// Expressions
const a = 5;
const b = 10;
console.log(`The sum of ${a} and ${b} is ${a + b}`);
// "The sum of 5 and 10 is 15"
// Function calls
function getFullName(first, last) {
return `${first} ${last}`;
}
console.log(`Welcome, ${getFullName('Jane', 'Smith')}!`);
// "Welcome, Jane Smith!"
Complex Expressions
// Ternary operators
const score = 85;
const result = `You ${score >= 70 ? 'passed' : 'failed'} the test!`;
console.log(result); // "You passed the test!"
// Object properties
const user = {
name: 'Alice',
account: {
type: 'premium',
balance: 1500,
},
};
console.log(
`${user.name} has a ${user.account.type} account with $${user.account.balance}`
);
// Array methods
const numbers = [1, 2, 3, 4, 5];
console.log(`Sum: ${numbers.reduce((a, b) => a + b, 0)}`);
console.log(`Average: ${numbers.reduce((a, b) => a + b, 0) / numbers.length}`);
// Nested template literals
const color = 'blue';
const html = `<div style="color: ${color}">${`Hello from ${color} text!`}</div>`;
Multi-line Strings
// Old way - concatenation or escaping
const multilineOld = 'Line 1\n' + 'Line 2\n' + 'Line 3';
// Template literal - natural multi-line
const multilineNew = `Line 1
Line 2
Line 3`;
// Practical example - HTML
const createCard = (title, content, footer) => `
<div class="card">
<h2 class="card-title">${title}</h2>
<div class="card-content">
${content}
</div>
<div class="card-footer">
${footer}
</div>
</div>
`;
// SQL queries
const userId = 123;
const status = 'active';
const query = `
SELECT * FROM users
WHERE id = ${userId}
AND status = '${status}'
ORDER BY created_at DESC
`;
// Markdown generation
const generateMarkdown = (data) => `
# ${data.title}
${data.description}
## Features
${data.features.map((f) => `- ${f}`).join('\n')}
## Installation
\`\`\`bash
npm install ${data.packageName}
\`\`\`
`;
Tagged Templates
Tagged templates allow you to parse template literals with a function.
// Basic tagged template
function myTag(strings, ...values) {
console.log(strings); // Array of string literals
console.log(values); // Array of interpolated values
return 'Processed string';
}
const name = 'John';
const age = 30;
const result = myTag`Hello ${name}, you are ${age} years old`;
// Custom processing
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i] !== undefined ? `<mark>${values[i]}</mark>` : '';
return result + str + value;
}, '');
}
const highlighted = highlight`Hello ${name}, welcome to ${'JavaScript'}!`;
console.log(highlighted);
// "Hello <mark>John</mark>, welcome to <mark>JavaScript</mark>!"
Advanced Tagged Templates
// SQL query builder
function sql(strings, ...values) {
const query = strings.reduce((result, str, i) => {
const value = i < values.length ? '?' : '';
return result + str + value;
}, '');
return {
query,
values: values.filter((v) => v !== undefined),
};
}
const userQuery = sql`
SELECT * FROM users
WHERE name = ${userName}
AND age > ${minAge}
AND status = ${status}
`;
console.log(userQuery);
// { query: "SELECT * FROM users WHERE name = ? AND age > ? AND status = ?",
// values: [userName, minAge, status] }
// CSS-in-JS style
function css(strings, ...values) {
const styles = strings.reduce((result, str, i) => {
const value = values[i] || '';
return result + str + value;
}, '');
return styles.trim().replace(/\s+/g, ' ');
}
const buttonStyles = css`
background-color: ${(props) => (props.primary ? 'blue' : 'gray')};
color: white;
padding: ${spacing.medium}px;
border-radius: ${borderRadius}px;
&:hover {
opacity: 0.8;
}
`;
Internationalization
// i18n tagged template
const translations = {
en: {
greeting: 'Hello',
farewell: 'Goodbye'
},
es: {
greeting: 'Hola',
farewell: 'Adiós'
}
};
function i18n(strings, ...values) {
const lang = getCurrentLanguage();
return strings.reduce((result, str, i) => {
let value = values[i];
// Translate if it's a translation key
if (value && value.startsWith('i18n:')) {
const key = value.slice(5);
value = translations[lang][key] || key;
}
return result + str + (value || '');
}, '');
}
const message = i18n`${${'i18n:greeting'}}, ${userName}!`;
Practical Use Cases
HTML Generation
// Component templates
function createUserCard(user) {
return `
<div class="user-card" data-id="${user.id}">
<img src="${user.avatar}" alt="${user.name}'s avatar" />
<h3>${user.name}</h3>
<p>${user.bio || 'No bio available'}</p>
<div class="stats">
<span>${user.followers} followers</span>
<span>${user.following} following</span>
</div>
${user.verified ? '<span class="verified">✓</span>' : ''}
</div>
`;
}
// Table generation
function createTable(headers, rows) {
return `
<table>
<thead>
<tr>
${headers.map((h) => `<th>${h}</th>`).join('')}
</tr>
</thead>
<tbody>
${rows
.map(
(row) => `
<tr>
${row.map((cell) => `<td>${cell}</td>`).join('')}
</tr>
`
)
.join('')}
</tbody>
</table>
`;
}
URL Building
// API endpoints
const API_BASE = 'https://api.example.com';
function buildUrl(endpoint, params = {}) {
const queryString = Object.entries(params)
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
.join('&');
return `${API_BASE}${endpoint}${queryString ? `?${queryString}` : ''}`;
}
// Dynamic URLs
const userId = 123;
const postId = 456;
const commentEndpoint = `/users/${userId}/posts/${postId}/comments`;
// With parameters
const searchUrl = `${API_BASE}/search?q=${encodeURIComponent(searchTerm)}&limit=${limit}`;
Error Messages
// Detailed error messages
class ValidationError extends Error {
constructor(field, value, requirement) {
super(
`
Validation failed for field '${field}'.
Value: ${JSON.stringify(value)}
Requirement: ${requirement}
`.trim()
);
this.name = 'ValidationError';
}
}
// Dynamic error reporting
function reportError(error, context) {
const report = `
Error Report
============
Time: ${new Date().toISOString()}
Environment: ${process.env.NODE_ENV}
Error Details:
- Name: ${error.name}
- Message: ${error.message}
- Stack: ${error.stack}
Context:
${Object.entries(context)
.map(([key, value]) => `- ${key}: ${JSON.stringify(value)}`)
.join('\n')}
`;
console.error(report);
}
Code Generation
// Generate functions
function createGetter(propertyName) {
return new Function('obj', `return obj.${propertyName}`);
}
// Generate classes
function generateClass(className, properties) {
return `
class ${className} {
constructor(${properties.join(', ')}) {
${properties.map((prop) => `this.${prop} = ${prop};`).join('\n ')}
}
${properties
.map(
(prop) => `
get${prop.charAt(0).toUpperCase() + prop.slice(1)}() {
return this.${prop};
}
set${prop.charAt(0).toUpperCase() + prop.slice(1)}(value) {
this.${prop} = value;
}`
)
.join('\n')}
}
`;
}
Advanced Patterns
Template Literal Utilities
// Remove indentation
function dedent(strings, ...values) {
const raw = strings.reduce((acc, str, i) => {
const value = values[i - 1];
return acc + value + str;
});
const lines = raw.split('\n');
const minIndent = lines
.filter((line) => line.trim())
.reduce((min, line) => {
const indent = line.match(/^(\s*)/)[1].length;
return Math.min(min, indent);
}, Infinity);
return lines
.map((line) => line.slice(minIndent))
.join('\n')
.trim();
}
// Usage
const code = dedent`
function example() {
console.log('Hello');
return 42;
}
`;
// String formatting
function format(template, data) {
return template.replace(/\${(.*?)}/g, (match, key) => {
const keys = key.trim().split('.');
let value = data;
for (const k of keys) {
value = value?.[k];
}
return value ?? match;
});
}
const template =
'Hello ${user.name}, you have ${user.messages.unread} unread messages';
const formatted = format(template, {
user: { name: 'John', messages: { unread: 5 } },
});
Safe HTML
// Escape HTML entities
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
// Safe HTML tagged template
function safeHtml(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i - 1];
const escaped = typeof value === 'string' ? escapeHtml(value) : value;
return result + escaped + str;
});
}
// Usage
const userInput = '<script>alert("XSS")</script>';
const safe = safeHtml`<p>User said: ${userInput}</p>`;
// <p>User said: <script>alert("XSS")</script></p>
Performance Optimization
// Cache template results
const templateCache = new Map();
function cachedTemplate(strings, ...values) {
const key = strings.join('|');
if (!templateCache.has(key)) {
templateCache.set(key, function (...vals) {
return strings.reduce((result, str, i) => {
return result + str + (vals[i] || '');
}, '');
});
}
return templateCache.get(key)(...values);
}
// Lazy evaluation
function lazy(strings, ...values) {
return () => {
return strings.reduce((result, str, i) => {
const value = typeof values[i] === 'function' ? values[i]() : values[i];
return result + str + (value || '');
}, '');
};
}
const expensiveCalc = () => {
console.log('Computing...');
return Math.random();
};
const lazyTemplate = lazy`Random: ${expensiveCalc}`;
// Computation happens only when called
console.log(lazyTemplate());
Common Pitfalls
Escaping Backticks
// Escape backticks inside template literals
const code = `const template = \`Hello \${name}\`;`;
// Or use String.raw
const path = String.raw`C:\Users\Documents\file.txt`;
console.log(path); // C:\Users\Documents\file.txt (backslashes preserved)
Expression Limitations
// Can't use statements, only expressions
// const invalid = `${if (true) { 'yes' }}`; // SyntaxError
// Use expressions instead
const valid = `${true ? 'yes' : 'no'}`;
// Or functions
const getStatus = () => {
if (condition1) return 'status1';
if (condition2) return 'status2';
return 'default';
};
const status = `Status: ${getStatus()}`;
Whitespace Handling
// Unwanted whitespace
const ugly = `
<div>
<p>Hello</p>
</div>
`;
// Better approach
const trimmed = `
<div>
<p>Hello</p>
</div>
`.trim();
// Or use a dedent function
const clean = dedent`
<div>
<p>Hello</p>
</div>
`;
Best Practices
-
Use for multi-line strings
const query = ` SELECT * FROM users WHERE active = true `;
-
Prefer over string concatenation
// Good const message = `Hello ${name}, you have ${count} messages`; // Avoid const message = 'Hello ' + name + ', you have ' + count + ' messages';
-
Use tagged templates for DSLs
const query = sql`SELECT * FROM users WHERE id = ${userId}`; const styles = css` color: ${theme.primary}; `;
-
Handle null/undefined values
const safe = `Value: ${value || 'N/A'}`; const safer = `Value: ${value ?? 'N/A'}`; // Preserves 0 and ''
Conclusion
Template literals transform string handling in JavaScript:
- String interpolation with
${}
- Multi-line strings without concatenation
- Tagged templates for advanced processing
- Cleaner syntax for complex strings
- Expression embedding for dynamic content
Key takeaways:
- Replace string concatenation with template literals
- Use tagged templates for domain-specific languages
- Handle whitespace carefully in multi-line strings
- Escape special characters when needed
- Essential for modern JavaScript development
Master template literals to write more expressive and maintainable code!