Twitter typeahead.js: Possible to use Angular JS as template engine? If not how do I replace "{{}}" for Hogan/Mustache js?

All we need is an easy explanation of the problem, so here it is.

I am working with twitter’s typeahead.js and I was wondering if it was possible to modify hogan.js to use something other than {{}}?

I am looking at the minified code now and I have no idea what to change for something so simple. Doing a find and replace breaks it.

I am asking this mainly because I’m using Angular JS but twitter’s typeahead requires a templating engine, causing hogan and angular’s {{}} to clash. An even better solution would be simply modifying Angular JS (I know it’s not a templating engine) and ditching Hogan to fit the following criteria:

Any template engine will work with typeahead.js as long as it adheres to the following API:

// engine has a compile function that returns a compiled template
var compiledTemplate = ENGINE.compile(template);

// compiled template has a render function that returns the rendered template
// render function expects the context to be first argument passed to it
var html = compiledTemplate.render(context);

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

Ignore the documentation on this, just look at the source code:

function compileTemplate(template, engine, valueKey) {
    var renderFn, compiledTemplate;
    if (utils.isFunction(template)) {
        renderFn = template;
    } else if (utils.isString(template)) {
        compiledTemplate = engine.compile(template);
        renderFn = utils.bind(compiledTemplate.render, compiledTemplate);
    } else {
        renderFn = function(context) {
            return "<p>" + context[valueKey] + "</p>";
        };
    }
    return renderFn;
}

It happens you can just pass a function to template, callable with a context object which contains the data you passed in the datum objects at the time of instantiation, so:

$('#economists').typeahead({
  name: 'economists',
  local: [{
    value: 'mises',
    type: 'austrian economist',
    name: 'Ludwig von Mises'
  }, {
    value: 'keynes',
    type: 'keynesian economist',
    name: 'John Maynard Keynes'
  }],
  template: function (context) {
    return '<div>'+context.name+'<span>'+context.type.toUpperCase()+'</span></div>'
  }
})

Method 2

If you want to use Hogan.js with Angular, you can change the delimiters by doing something like:

var text = "my <%example%> template."
Hogan.compile(text, {delimiters: '<% %>'});

Method 3

It appears that the template engine result that typeahead.js expects is an html string and not the dom element (in dropdown_view.js). So I am not sure there is a good solution for using an angular template. As a test I was able to get it binding the result to an angular template but it has to render to an element and then get the html value from the element after binding with the data. I don’t like this approach but I figured someone might find it useful. I think I will go with a template function like in the previous post.

Jade template looks like

.result
  p {{datum.tokens}}
  p {{datum.value}}

Directive

angular.module('app').directive('typeahead', [
  '$rootScope', '$compile', '$templateCache',
  function ($rootScope, $compile, $templateCache) {
    // get template from cache or you can load it from the server
    var template = $templateCache.get('templates/app/typeahead.html');
    var compileFn = $compile(template);
    var templateFn = function (datum) {
      var newScope = $rootScope.$new();
      newScope.datum = datum;
      var element = compileFn(newScope);
      newScope.$apply();
      var html = element.html();
      newScope.$destroy();
      return html;
    };
    return {
      restrict: 'A',
      link: function (scope, element, attrs, ctrl) {
        element.typeahead({
          name: 'server',
          remote: '/api/search?q=%QUERY',
          template: templateFn
        });
        element.on('$destroy', function () {
          element.typeahead('destroy');
        });
        element.on('typeahead:selected', function () {
          element.typeahead('setQuery', '');
        });
      }
    };
  }
]);

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply