Skip to content

Instantly share code, notes, and snippets.

@simeonwillbanks
Last active June 25, 2019 06:32
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save simeonwillbanks/5708448 to your computer and use it in GitHub Desktop.
Save simeonwillbanks/5708448 to your computer and use it in GitHub Desktop.
#AngularJS templates and #rails with eager loading -- prefill the AngularJS $templateCache. Inspired by @minhajuddin -- http://minhajuddin.com/2013/04/28/angularjs-templates-and-rails-with-eager-loading
# /code/railsapp/app/assets/javascripts/thing/app.js.coffee
#= require angular/templates
angular.module("thing", ["app.templates"]).value("appName", "thing")

AngularJS app JavaScript keyed by app name thing

/code/railsapp/app/assets/javascripts/thing/

AngularJS app templates keyed by app name thing

/code/railsapp/app/assets/templates/thing/

Shared AngularJS modules

/code/railsapp/lib/assets/javascripts/angular/

<%
# /code/railsapp/lib/assets/javascripts/angular/templates.js.coffee.erb
template_cache = []
# Prefill templateCache with latest templates
if Rails.env.production? || Rails.env.staging?
Dir.glob(Rails.root.join("app", "assets", "templates", "**", "*.haml")).each do |f|
pathname = Pathname(f)
template = {}
# /code/railsapp/app/assets/templates/thing -> assets/thing
template[:dirname] = pathname.dirname.sub(Rails.root.join("app", "assets", "templates").to_s, "assets")
# /code/railsapp/app/assets/templates/thing/page.html.haml -> page.html
template[:basename] = pathname.basename(".haml")
# ["/", "assets/thing", "page.html"] -> /assets/thing/page.html
template[:path] = File.join("", template[:dirname], template[:basename])
# Render HAML to HTML
template[:markup] = Haml::Engine.new(pathname.read).render
template_cache << template
end
end
%>
angular.module("app.templates", []).run(["$templateCache", "appName", ($templateCache, appName) ->
templates = [
<% template_cache.each do |template| %>
path: "<%= template[:path] %>"
markup: """
<%= template[:markup] %>
"""
,
<% end %>
]
appPath = "<%= File.join("", "assets", "") %>" + appName
# Only prefill the cache for the current app
$templateCache.put(template.path, template.markup) for template in templates when template.path.indexOf(appPath) == 0
])
@simeonwillbanks
Copy link
Author

I ran into a gotcha with this implementation.

%input.input-block-level{ type: 'text', ng_model: 'obj.prop', ng_pattern: '/[0-9]/' }

😡 The regular expression was clobbered when compiled from HAML > HTML > CoffeeScript > JavaScript. 😡

The solution is to change the expression from a regex literal to a function.

%input.input-block-level{ type: 'text', ng_model: 'obj.prop', ng_pattern: 'obj.patternToMatch()' }

Now, the regular expression isn't sent through all the compilers.

@ibrahima
Copy link

I don't quite understand what the purpose of the keying with appName is. Do you have multiple AngularJS apps inside one Rails app, and they're being served templates from different paths, so you only want to load the templates from the relevant path? For myself I just removed the when clause on line 34 because I've only got one AngularJS app, but I'm curious how your Rails app is structured and what benefits this provides. Thanks for writing this up though, it's really helpful! Thus far it seems to be the only good way I've found of deploying HAML based templates for an AngularJS app inside a Rails app.

@edgahan
Copy link

edgahan commented Oct 17, 2013

If you've arrived here from: http://minhajuddin.com/2013/04/28/angularjs-templates-and-rails-with-eager-loading and if you want templates.js.erb to reload when /angular/directives/some_directive_template.html changes then you can add this https://gist.github.com/edgahan/7023047 to config/initializers/sprockets.rb

And add templates.js to the precompile asset array in application.rb, and on my layout/application.html file I have something along the lines of

<script src="application.js"> <script src="templates.js"> Then within templates.js.erb add //= depend_on_tree ./angular/directives/

@MrOrz
Copy link

MrOrz commented Nov 7, 2013

I changed the HAML rendering part with ERB renderer.

# Render ERB to HTML
template[:markup] = ERB.new(pathname.read, nil, nil, 'tmpl').result(binding)

And it worked for my ERB templates :)

@kimardenmiller
Copy link

Our app is just a single app, so the appName references were not needed.

Additionally, as credited elsewhere, you need depend_on(f) to keep the cache changing when your templates change:

<%
template_cache = []
# Pre-fill templateCache with latest templates for all environments
if Rails.env.production? || Rails.env.staging? || Rails.env.development?
  Dir.glob(Rails.root.join("app", "assets", "templates", "**", "*.haml")).each do |f|
    depend_on(f)
    pathname = Pathname(f)
    template = {}
    # /code/railsapp/app/assets/templates/thing -> assets/thing
    template[:dirname] = pathname.dirname.sub(Rails.root.join("app", "assets", "templates").to_s, "assets")
    # /code/railsapp/app/assets/templates/thing/page.html.haml -> page.html
    template[:basename] = pathname.basename(".haml")
    # ["/", "assets/thing", "page.html"] -> /assets/thing/page.html
    template[:path] = File.join("", template[:dirname], template[:basename])
    # Render HAML to HTML
    template[:markup] = Haml::Engine.new(pathname.read).render
    template_cache << template
  end
end
%>
angular.module('spokenvote.templates', []).run(['$templateCache', ($templateCache) ->
    templates = [
        <% template_cache.each do |template| %>
        path: "<%= template[:path] %>"
        markup: """
                <%= template[:markup] %>
                """
    ,
        <% end %>
    ]

    # Pre-fill the Angular cache
    $templateCache.put(template.path, template.markup) for template in templates
])

Finally, I'd love to see an example of this logic moved into a helper method :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment