For the last year, the Monterail team has been using AngularJS and Rails together. I'd like to share with you some of the experiences that we've gained throughout this process.

If you don't want to read, then go ahead and dive into our sample application.

We are using rails-assets

# Gemfile source 'https://rubygems.org' source 'https://rails-assets.org' # etc .. # assets gem 'rails-assets-lodash' gem 'rails-assets-angular' , '~> 1.2.0' gem 'rails-assets-angular-cache' gem 'rails-assets-angular-ui-router' , '~> 0.2.9' gem 'rails-assets-angular-translate'

We are passing configuration by JsEnv module and AngularJS constant

# lib/templates_paths.rb module TemplatesPaths extend self def templates Hash [ Rails . application . assets . each_logical_path . select { | file | file . end_with? ( 'swf' , 'html' , 'json' ) }. map { | file | [ file , ActionController :: Base . helpers . asset_path ( file )] } ] end end # app/controllers/concerns/js_env.rb require 'templates_paths' module JsEnv extend ActiveSupport :: Concern include TemplatesPaths included do helper_method :js_env end def js_env data = { env: Rails . env , templates: templates } <<- EOS . html_safe < script type = "text/javascript" > shared = angular.module('SampleApp') shared.constant('Rails', #{ data . to_json } ) </script> EOS end end # app/controllers/application_controller.rb class ApplicationController < ActionController :: Base protect_from_forgery with: :exception include JsEnv end

// app/views/layouts/application.html.slim body h1 Sample App Main Page = yield = javascript_include_tag 'application' = js_env

# app/assets/javascripts/controllers/pages_ctrl.coffee angular . module ( 'SampleApp' ). controller 'PagesCtrl' , ( $scope , Rails ) -> $scope . test = Rails . env

Yes, we use coffeescript and we use ng-min as well.

We take advantage of sprockets and AngularJS interceptors

# config/initializers/sprockets.rb # register .slim for assets pipeline Rails . application . assets . register_mime_type 'text/html' , '.html' Rails . application . assets . register_engine '.slim' , Slim :: Template

We put slim templates under the app/assets/templates directory.

With a JsEnv and small AngularJS interceptor we will always get the right path for our template, even on production after the rake assets:precompile .

# app/assets/javascripts/init.coffee angular . module ( 'SampleApp' ). config ( $provide , $httpProvider , Rails ) -> # Assets interceptor $provide . factory 'railsAssetsInterceptor' , ( $angularCacheFactory ) -> request : ( config ) -> if assetUrl = Rails . templates [ config . url ] config . url = assetUrl config $httpProvider . interceptors . push ( 'railsAssetsInterceptor' )

Throughout the whole AngularJS application we can use asset paths normally like: /pages/index.html . The railsAssetsInterceptor factory will translate asset paths to their version after compilation. It changes the asset path from /pages/index.html to /assets/pages/index-sha.html .

Yes, this works. Check it out!

We are using angular-translate with custom loader

# app/assets/javascripts/init.coffee angular . module ( 'SampleApp' , [ 'pascalprecht.translate' ]) . factory 'railsLocalesLoader' , ( $http ) -> ( options ) -> $http . get ( "locales/ #{ options . key } .json" ). then ( response ) -> response . data , ( error ) -> throw options . key . config ( $translateProvider ) -> $translateProvider . useLoader ( 'railsLocalesLoader' ) $translateProvider . preferredLanguage ( 'en' )

railsLocalesLoader is a custom factory for loading locales from Rails. We serve locales via the assets pipeline in the same manner that we do with templates. It works after the translation has changed in the config/locales/[KEY].yml file and works properly after rake assets:precompile . This is possible thanks to JsEnv and railsAssetsInterceptor .

The Rails part of the code looks like this:

# config/initializers/sprockets.rb # add custom depend_on_config sprockets processor directive class Sprockets :: DirectiveProcessor def process_depend_on_config_directive ( file ) path = File . expand_path ( file , Rails . root . join ( 'config' )) context . depend_on ( path ) end end # register .json for assets pipeline Rails . application . assets . register_mime_type 'application/json' , '.json' # enable to use sprockets directive processor in .json Rails . application . assets . register_preprocessor 'application/json' , Sprockets :: DirectiveProcessor

Put locale under the app/assets/locales/locales directory.

// app/assets/locales/locales/en.json.erb //= depend_on_config locales/en.yml <%= Translations . new . for ( :en ). to_json %>

In the code above we use a custom depend_on_config directive which relays on sprockets depend_on directive. Thanks to depend_on_config directive, we can expire an asset's cache in response to a change in yaml file.

Translations service in ruby prepares flatten hash from your locale yaml file. You can find an example implementation here.

We are using client side cache

Before, I explained some of the magic behind how sprockets and AngularJS work together, thanks to server side cache solutions. Now we can just as easily cache templates on the client side to get an even greater boost.

# app/assets/javascripts/init.coffee angular . module ( 'SampleApp' , [ 'jmdobry.angular-cache' , ]) . config ( $provide , Rails ) -> # Template cache if Rails . env != 'development' $provide . service '$templateCache' , [ '$angularCacheFactory' , ( $angularCacheFactory ) -> $angularCacheFactory ( 'templateCache' , { maxAge : 3600000 * 24 * 7 , storageMode : 'localStorage' , recycleFreq : 60000 }) ]

This may not be a lot of words, but it is a lot of code so I hope it will be useful.