ES6+ FeaturesFeatured

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.

By JavaScriptDoc Team
template literalses6stringsjavascriptinterpolation

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: &lt;script&gt;alert("XSS")&lt;/script&gt;</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

  1. Use for multi-line strings

    const query = `
      SELECT * FROM users
      WHERE active = true
    `;
    
  2. Prefer over string concatenation

    // Good
    const message = `Hello ${name}, you have ${count} messages`;
    
    // Avoid
    const message = 'Hello ' + name + ', you have ' + count + ' messages';
    
  3. Use tagged templates for DSLs

    const query = sql`SELECT * FROM users WHERE id = ${userId}`;
    const styles = css`
      color: ${theme.primary};
    `;
    
  4. 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!