The Odd Couple: Server- and client-side templating, together at last

With the recent growth of mega, do-everything client-side frameworks that include their own partial template rendering mechanisms (such as angular, ember, react, etc. etc. etc.), the potential of running into interpolation pattern clashes between your servers-side template renderer and your client-side framework renderer is high. Of course, the obvious solution is to do a true single-page application where the client-side framework is responsible for all view changes, so that there's no potential conflict. But for those who rely on SEO - where the page has to be rendered by the server (at least until crawlers catch up with technology) and not by client-side javascript - you've probably experienced this problem: you need to do some sort of client-side interpolation on a page that's rendered by the server. The server sees your interpolation and renders it . . . as nothing (because there's no corresponding variable). Or worse, it blows up because it contains characters legal for the client-side framework but not for the server-side templating engine. I recently found there is an extremely simple way to solve this problem.

Where I work, SEO is extremely important. So we serve traditional HTML pages generated by the server using Hogan.js (hogan-express specifically). We also use angular on the client-side for dynamic content. Both of these engines use the double curly brace interpolation pattern. That means, if you're working in a server-side template, you can't use variables computed by angular controllers because Hogan will try to render them.

<!-- Hogan will look for this variable in its own context tree, but of course, it won't find it, thus rendering it as ""-->  
<div some-attr="{{ some.angular.variable }}"></div>  

We've had some super creative hacks to get around this. Some possibilities include:

  1. Move this to an angular partial and reference it via ng-include (or whatever the equivalent is for your particular frontend framework). This is fine as long as you don't need SEO exposure for this exact element. If you do though . . . proceed to number two.
  2. Depending on what you're needing to interpolate, with angular, it may be possible to do it without interpolation. For instance, if it's the content of the element, you can use ng-bind: <div ng-bind="some.content"></div>. In my case, the attribute that led me down this path was ng-href which requires the interpolation pattern, so this was not a possibility.
  3. In some templating libraries you can escape or change the interpolation pattern mid-stream. Here's what that would look like in Hogan:
{{=<% %>=}}
<!-- This works now because Hogan is looking for "<% %>" instead -->  
<div some-attr="{{ some.angular.variable }}"></div>  
<%={{ }}=%>  

Oh yeah, that's totally awesome looking. I want that floating all over my code base.

It turns out there's a much simpler and better way. Two actually, but one I feel is definitively better than the other.

Most languages have a way of registering your own filters (or lambdas), so you can write one for injecting interpolation for your client-side framework. In Hogan, that looks like this:

// This example is in an express app, using "hogan-express",
// which has a concept of "lambdas," which are similar to
// filters but simpler.
res.locals.lambdas.angular = function(str) {  
  return '{{ ' + str + ' }}';
};

You can use this filter like this:

<div some-attr="{{#lambdas.angular}}some.angular.variable{{/lambdas.angular}}"></div>  

The lambda takes whatever string is passed in and wraps it in double curly braces, but since this is post render, it will be left for angular to pick up. This particular example is rendered as:

<div some-attr="{{ some.angular.variable }}"></div>  

which means that angular will be able to correctly render it on the client side.

The alternative method, mentioned above, is to set your start and end pattern as Hogan variables, something like this:

res.locals.hoganStart = '{{';  
res.locals.hoganEnd = '}}';  

and then use them like this:

<div some-attr="{{hoganStart}} some.angular.variable {{hoganEnd}}"></div>  

This also works, but I'll argue it's inferior for two reason: first, because it's a little less obvious what is happening, and second, because this method is not unit testable like the first is.

It's interesting, once we came up with this method for "escaping" interpolation meant to happen on the client-side, it seemed really obvious. I can't believe we didn't think of it before. But in searching for ways to do this, it never came up on stackoverflow or other resources as a way to handle this problem. So I'm sharing this with you now so that you too can enjoy interpolation bliss and not spend hours searching the internet thinking "There has to be a better way to do this." There is. This is it.

comments powered by Disqus