6

In python, you can do variable string interpolation like this:

song_context = { "adjective": "funny" }
ella_sings = "my {adjective} valentine".format(**song_context)

Here, the song_context object formats variables in the ella_sings string.

In ES6 is there a built-in way to do something like this with template literals? I'm looking for a quick way to explicitly define a substitution space for a given string. Ex:

const song_context = { adjective: "funny" }
const ella_sings = `my ${adjective} valentine`.format(song_context)

Context: I know other ways to do this, e.g. use a template library or do multiple find and replace, but am wondering if any portion of ES6 supports this use case out of the box. I skimmed the template literal part of the ECMAScript 6.0 standard and it pretty clearly states "Let ctx be the running execution context" but it seems kind of hard to believe they wouldn't provide a way to be more explicit about the context when necessary.

Myer
  • 3,670
  • 2
  • 39
  • 51
  • why are you putting adjective inside an object to use with a string literal? – Robert Mennell Sep 26 '17 at 19:00
  • 1
    Template literals are not a replacement for an actual template engine. – Felix Kling Sep 26 '17 at 19:00
  • @RobertMennell that's the question - I want to pass an object to a template literal instead of using the template literal's context. – Myer Sep 26 '17 at 19:11
  • @FelixKing I'm not sure why you thought I was trying to replace an actual template engine by using template literals. I'm not seeking to do this. Instead, I want to control the scope of a template literal. – Myer Sep 26 '17 at 19:12
  • @P.MyerNore then my answer should be exactly what you want. In base template literal format you can't, but in a tagged template you can. – Robert Mennell Sep 26 '17 at 19:18
  • Well, it looks like what you want is a template engine (or whatever you want to call that `.format` feature). – Felix Kling Sep 26 '17 at 19:44

3 Answers3

7

In ES6 is there a built-in way to do something like this with template literals?

Template literals have no special logic. The code between the ${..} brackets is just a normal expression that gets evaluted to a value.

`my ${adjective} valentine`

is always going to look for a variable named adjective in the local scope. You can certainly change it to do

`my ${song_context.adjective} valentine`

If you don't want to change that part of it, then you can always use destructuring, e.g.

const {adjective} = song_context;
const ella_sings = `my ${adjective} valentine`

or use an IIFE:

const ella_sings = ({adjective}) => `my ${adjective} valentine`)(song_context);

You can also build your own templating system around template literals if you want, but it starts getting complicated fast and loses most of the benefits that make you want to use template literals in the first place.

loganfsmyth
  • 156,129
  • 30
  • 331
  • 251
2

You might want to read up a bit more on them over at the Javascript MDN and pay attention to the section called Tagged template literals as it shows how you can turn a template literal into a full on template.

For archival purposes I'm going to import the relevant section:

Tagged template literals

A more advanced form of template literals are tagged template literals. Tags allow you to parse template literals with a function. The first argument of a tag function contains an array of string values. The remaining arguments are related to the expressions. In the end, your function can return your manipulated string (or it can return something completely different as described in the next example). The name of the function used for the tag can be named whatever you want.

var person = 'Mike';
var age = 28;

function myTag(strings, personExp, ageExp) {

  var str0 = strings[0]; // "that "
  var str1 = strings[1]; // " is a "

  // There is technically a string after
  // the final expression (in our example),
  // but it is empty (""), so disregard.
  // var str2 = strings[2];

  var ageStr;
  if (ageExp > 99){
    ageStr = 'centenarian';
  } else {
    ageStr = 'youngster';
  }

  return str0 + personExp + str1 + ageStr;

}

var output = myTag`that ${ person } is a ${ age }`;

console.log(output);
// that Mike is a youngster

Tag functions don't need to return a string, as shown in the following example.

function template(strings, ...keys) {
  return (function(...values) {
    var dict = values[values.length - 1] || {};
    var result = [strings[0]];
    keys.forEach(function(key, i) {
      var value = Number.isInteger(key) ? values[key] : dict[key];
      result.push(value, strings[i + 1]);
    });
    return result.join('');
  });
}

var t1Closure = template`${0}${1}${0}!`;
t1Closure('Y', 'A');  // "YAY!"
var t2Closure = template`${0} ${'foo'}!`;
t2Closure('Hello', {foo: 'World'});  // "Hello World!"

These tagged template literals seem to be exactly what you want.

Robert Mennell
  • 1,954
  • 11
  • 24
1

You can create a function that compiles a template and then accepts a context as its parameter. The only catch is you'll need to put quotes around the placeholders, inside the curly braces.

const compileTemplate = (template, ...replacements) => 
    ctx => 
        template.reduce(
            (accumulator, part, i) => 
                accumulator + ctx[replacements[i - 1]] + part

            );


const templateFn = compileTemplate`This is a ${'adj'} problem.`;

console.log(templateFn( {adj: 'hard'} ));
console.log(templateFn( {adj: 'funny'} ));
Samuel Neff
  • 73,278
  • 17
  • 138
  • 182