Compile AngularJS Anywhere

Written by Chris Sloan

The Rails app front-end that we develop on at UserTesting is not your typical setup. When first created, it utilized BackboneJS (and still does), but not in its true sense. BackboneJS was there for use of its inheritance of the DOM and how it makes working with objects a breeze. Where BackboneJS shines is with single page apps, but unfortunately on our site, we do not have any application currently for single page functionality.

So where does AngularJS fit in?

Skip ahead about two years and AngularJS hits the scene and gains popularity. We start to see its benefits, again outside of the single page application, and decide to bake it into our app with any new features. BackboneJS is still there and plays nicely with AngularJS on an as needed basis.

So what about this compiling stuff?

Like mentioned above, a lot of our app comprises BackboneJS views that render handlebar templates on the fly. Recently, I ran into a feature we were building where I wanted to render an AngularJS directive inside a dynamic handlebars template. Since AngularJS has already bootstrapped before the template is loaded — which is from a click event from a user, the directive never gets initialized.

I found a pretty simple extensible way to allow for this to happen. I stumbled across this gist that was suited just for BackboneJS. I decided to take this as an example and convert it to vanilla JavaScript for ease of portability for use in our application.

Below is the coffeescript class for brevity purposes.

window.AngularCompiler = class AngularCompiler
  constructor: (element) ->
    @element           = element
    @compiled_template = ""

  compile: ->
    _that = @
    $injector = angular.element('body').injector()
    $injector.invoke ($rootScope, $compile) ->
      _that.compiled_template = $compile(_that.element)($rootScope)
      $rootScope.$apply()
      return

    return @compiled_template

Our AngularJS app is bootstrapped on the body element of the DOM, hence why we are calling out the element injector there. Also, we use $rootScope here to compile against because we are grabbing the injector from our main app. This script could easily be changed up to allow for other angular elements to compile scope against instead of the full app, but at this time, it is not needed.

Next, take your element, throw it through the compiler and AngularJS will do the rest.

new AngularCompiler("<div><my-angular-directive></my-angular-directive></div>").compile()

All your directive events, scope, etc. will now be bound and functional!