Download as pdf or txt
Download as pdf or txt
You are on page 1of 11

8/11/2014 How to integrate AngularJS with Rails 4 - Blog - Shelly Cloud

https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4 1/11
Shelly Cloud
Pricing
Support
Docs
Blog
Sign up
Log in
How to integrate AngularJS with Rails 4
Posted on October 1, 2013 by Micha Kwiatkowski and has 34 comments
Building most single-page applications (SPAs for short) is a two-step process: first you create a JSON API in a backend
technology of choice and then you use that API in the JavaScript application. Here we'll be using Ruby on Rails on the
backend and AngularJS on the frontend.
The main pain point of any kind of integration is making sure that everything fits together well. This post will not take you
through building the whole application. Instead, it will focus on making sure all the integration points are handled properly.
I will also share with you some practical advice on the topic.
Code examples used in this post come from a Todo list management application. This text summarizes all the lessons
learned during writing of that app.
Building a JSON API in Rails
Building an API in Rails is easy, so we'll roll our own from scratch. Note that if you decide to use a specialized library like
angularjs-rails-resource some details will differ, but the general idea will remain the same.
Routing
Let's start by defining routes for our API.
namespace :api, defaults: {format: :json} do
resources :task_lists, only: [:index] do
resources :tasks, only: [:index, :create, :update, :destroy]
end
end
This is all pretty standard. We can get all lists through the task_lists#index action, get a task listing for a specific list via
tasks#index action and operate on specific tasks via create, update and destroy actions. Using format: :json is a handy
default.
If we run rake routes now, we will get an output similar to this:
GET /api/task_lists/:task_list_id/tasks(.:format) api/tasks#index {:format=>:json}
POST /api/task_lists/:task_list_id/tasks(.:format) api/tasks#create {:format=>:json}
PATCH /api/task_lists/:task_list_id/tasks/:id(.:format) api/tasks#update {:format=>:json}
PUT /api/task_lists/:task_list_id/tasks/:id(.:format) api/tasks#update {:format=>:json}
DELETE /api/task_lists/:task_list_id/tasks/:id(.:format) api/tasks#destroy {:format=>:json}
GET /api/task_lists(.:format) api/task_lists#index {:format=>:json}
There are two HTTP verbs corresponding to the update action: PATCH and PUT. Supporting PATCH is a new feature
added in Rails 4.0. You can read more about it on the offical blog.
Request parameters
Rails 4 also changed the way the mass-assignment protection is done. Instead of whitelisting/blacklisting parameters in the
model, you now have to do it in the controller, using require and permit methods. I like to create a helper method than
can be used both in create and update actions:
def safe_params
params.require(:task).permit(:description, :priority, :completed)
end
8/11/2014 How to integrate AngularJS with Rails 4 - Blog - Shelly Cloud
https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4 2/11
With this definition, action implementation looks as simple as this:
def create
task = task_list.tasks.create!(safe_params)
render json: task, status: 201
end
def update
task.update_attributes(safe_params)
render nothing: true, status: 204
end
Generating JSON
The previous example already hinted at this: returning JSON output should be as simple as writing render json:
object. I like to use active_model_serializers gem which greatly simplifies the process. Whenever you render an object or
a collection of objects to json, a proper serializer will be used. In case of our Todo list application, the following will render
an array of tasks:
render json: TaskList.find(params[:id]).tasks
To get the exact format we want (that will be easy to consume by AngularJS), after installing the gem, we also need to
configure it. Put the following into config/initializers/active_model_serializers.rb:
ActiveSupport.on_load(:active_model_serializers) do
# Disable for all serializers (except ArraySerializer)
ActiveModel::Serializer.root = false
# Disable for ArraySerializer
ActiveModel::ArraySerializer.root = false
end
With this configuration in place and a serializer defined like that:
# app/serializers/task_serializer.rb
class TaskSerializer < ActiveModel::Serializer
attributes :id, :description, :priority, :due_date, :completed
end
we'll get the following output:
[
{'id' => 123,
'description' => 'Send newsletter',
'priority' => 2,
'due_date' => '2013-09-10',
'completed' => true},
{'id' => 124,
'description' => 'Prepare presentation',
'priority' => 1,
'due_date' => '2013-09-17',
'completed' => false}
]
Testing
All respectable APIs have to be well tested. Fortunately, Rails makes writing automated tests really easy. In case of a
JSON API, controller tests are the way to go. That's how a sample test may look like, using RSpec syntax:
describe Api::TasksController do
it "should be able to create a new record" do
post :create, task_list_id: task_list.id,
task: {description: "New task"}, format: :json
response.should be_success
JSON.parse(response.body).should == {'id' => 123, ...}
end
end
An important detail to note here is the use of format: :json. This makes sure that the parameters are passed and
interpreted as JSON.
8/11/2014 How to integrate AngularJS with Rails 4 - Blog - Shelly Cloud
https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4 3/11
When writing more tests like this, you may find it useful to define a helper method for parsing the response. Put the
following into your spec_helper.rb:
module JsonApiHelpers
def json_response
@json_response ||= JSON.parse(response.body)
end
end
RSpec.configure do |config|
config.include JsonApiHelpers, type: :controller
end
With this code in place, instead of:
JSON.parse(response.body).should == {...}
you can now write:
json_response.should == {...}
which is a little cleaner, you must admit. It also has an added bonus that the response will be only parsed once, even if you
make multiple assertions on the output.
Building AngularJS application
Since the API is ready it's finally time to move on to building the AngularJS application. There is a breadth of tutorials to
watch and read, so I'm not going to repeat that here, instead focusing solely on the integration with Rails.
Including AngularJS files
The fastest way to get started is putting the JavaScript include tags for AngularJS directly into layout. At the time of writing
this post, 1.0.8 is the latest stable version, so if you want to use that, put the following two lines into
app/views/layouts/application.html.slim:
= javascript_include_tag "//ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"
= javascript_include_tag "//ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular-resource.min.js"
Of course you can also download the files and put them somewhere in app/assets/javascripts/. Unfortunately the
asset pipeline may break some of your AngularJS code due to renaming. To prevent that, put the following line into your
config/environments/production.rb:
config.assets.js_compressor = Uglifier.new(mangle: false)
This will disable name mangling during JavaScript minification. You can read more about this topic in the official tutorial
(scroll down to "A Note on Minification").
Structuring the AngularJS code
Each AngularJS application consists of the main application module and some controllers, directives and services. As long
as you keep everything under app/assets/javascripts/ the asset pipeline will put them all together without a problem.
Ultimately it's up to you where to put each of them, but here's how I've done it.
First, my application.js lists all the external requirements (like jQuery or AngularJS itself), then the file containing the
main application module, to finally use the require_tree directive:
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require lib/angular.min
//= require lib/angular-resource.min
//= require todoApp
//= require_tree .
With that in mind, the main application module is defined in todoApp.js.coffee and looks like this:
todoApp = angular.module('todoApp', ['ngResource'])
8/11/2014 How to integrate AngularJS with Rails 4 - Blog - Shelly Cloud
https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4 4/11
I keep the rest of the files in suitable subdirectories: controllers, directives and services for standard elements of an
AngularJS app, and lib for any other dependencies.
Defining the service
The Rails API can be accessed from the AngularJS app through the ngResource module. Instead of using the resource
directly in the controller, it's a good practice to define a service around it. This way you can abstract away some pesky
details of accessing data, much like you would do with Rails models.
Below is a basic service for accessing tasks, written in CoffeeScript.
angular.module('todoApp').factory 'Task', ($resource) ->
class Task
constructor: (taskListId) ->
@service = $resource('/api/task_lists/:task_list_id/tasks/:id',
{task_list_id: taskListId, id: '@id'})
create: (attrs) ->
new @service(task: attrs).$save (task) ->
attrs.id = task.id
attrs
all: ->
@service.query()
For example, to get a list of all tasks from a given list, you'd do the following:
$scope.tasks = Task(taskListId).all()
It cannot get any easier than this.
Making it work with CSRF protection
Rails come with cross-site request forgery protection in the form of a token embeded in the head section of each page. To
make forms work in AngularJS you need to use that token in all API requests. Put the following three lines into the main
application file (todoApp.js.coffee in our case):
todoApp.config ($httpProvider) ->
authToken = $("meta[name=\"csrf-token\"]").attr("content")
$httpProvider.defaults.headers.common["X-CSRF-TOKEN"] = authToken
Making it work with turbolinks
Turbolinks which became a default in Rails 4 may cause some problems to AngularJS applications, especially if you need
to support different SPAs on multiple pages. To overcome this problem, put the following into the main application file:
$(document).on 'page:load', ->
$('[ng-app]').each ->
module = $(this).attr('ng-app')
angular.bootstrap(this, [module])
This will make sure the AngularJS application is properly initialized each time a turbolink does its fetch&replace magic.
Making updates using the PATCH method
The new PATCH method mentioned in the beginning of this post is not supported by ngResource by default, but it's easy
enough to make it work. First, put the following code into the main application file:
defaults = $http.defaults.headers
defaults.patch = defaults.patch || {}
defaults.patch['Content-Type'] = 'application/json'
It will ensure any PATCH requests are made with application/json content type.
After that, modify the resource definition from before, so that it specifies PATCH as a prefered verb for the update action.
$resource('/api/task_lists/:task_list_id/tasks/:id',
{task_list_id: taskListId, id: '@id'},
8/11/2014 How to integrate AngularJS with Rails 4 - Blog - Shelly Cloud
https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4 5/11
{update: {method: 'PATCH'}})
Now, whenever you issue an update on the resource, it will properly submit a PATCH request with JSON content.
Testing
Just as Rails, AngularJS has a great testing story. Thanks to its focus on Dependency Injection, unit testing components of
an AngularJS application is a breeze.
The official tutorial walks you through setting up testing infrastructure, using Karma, so I'm not going to repeat that here. I
found it easy to use with Jasmine which I already knew and with angular-mocks which helps with mocking some features
of a web browser.
Debugging
When testing fails it's often useful to be able to boot up the browser and poke around manually. As I was learning
AngularJS and figuring out integration problems, Misko Hevery's answer on Stackoverflow was a big help to me.
Turns out, inspecting AngularJS app internals from the browser is not that complicated. All you need to do is to grab an
element with jQuery. For example, that's how you can access scope in the context of the taskDescription element:
$("#taskDescription").scope()
From there you can traverse the complete state of your controller.
Another tool that may come in handy is AngularJS Batarang, a Chrome extension that allows you to inspect and profile
your SPA's internals.
Now go and build!
That should get you through the initial steps of building your dream single-page application.
Leave your thoughts in the comments and if you need a hosting for your Rails backend you don't have to look far. :)
Shelly Cloud is a platform for hosting Ruby and Ruby on Rails applications. You can focus on development without
getting distracted by deployment, optimization and maintenance.
Deploy your app now, for free
Tweet 166
40
submit
34 Comments Shelly Cloud Blog Login
Sort by Best Share
Join the discussion
Reply
dude 10 months ago
Expect syntax, yo.

9
Alexander Ross 9 months ago
Instead of `config.assets.js_compressor = Uglifier.new(mangle: false)` you can tell angular which object you
are injecting. Se attached image.
Favorite
Share
45 Like Share
8/11/2014 How to integrate AngularJS with Rails 4 - Blog - Shelly Cloud
https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4 6/11
Reply 6
Reply
Micha Kwiatkowski 9 months ago Alexander Ross
True, although I personally prefer to modify one setting than to use a more verbose syntax in all of
my modules. ;)

3
Reply
mattdrobertson 10 months ago
Nice post! It would have been nice if you went a little more in depth about how you handle angular views /
templates (where do you put them? do you serve them from the asset pipeline? etc).
Also routing is interesting because it is provided both by Angular and by Rails

3
Reply
Micha Kwiatkowski 9 months ago mattdrobertson
Thanks!
Handling AngularJS views in Rails is probably a topic worth of another blog post. ;) In the context of
this todo app, Gosia did the necessary work in her pull request: https://github.com/mkwiatkowsk...
Especially the following two commits are relevant to what you're asking about:
- https://github.com/mkwiatkowsk...
- https://github.com/mkwiatkowsk...
I hope that helps.


Reply
alex 9 months ago
Great post and nice to see it all integrated in the todo app complete with the csrf and devise.

1
Reply
andreareginato 10 months ago
I would love to see more about testing Angualar stuff inside a rails integration test, if you do so. And if you
don't, it would be great to grab the code and better understand how you handle this.

1
Reply
Micha Kwiatkowski 9 months ago andreareginato
Testing AngularJS apps inside Rails integration test doesn't differ from testing any other pages with
JS components. Personally I like to use capybara with poltergeist driver.


Reply
Selasie Hanson 10 months ago
In order to prevent the asset pipeline from breaking your code due to how angular code is written, you can
add ngmin-rails to your gem file. https://github.com/jasonm/ngmi...

1
Reply
Micha Kwiatkowski 9 months ago Selasie Hanson
Sure, that's an alternative to initializing Uglifier with mangle set to false.


Nick Shook 10 months ago
fantastic article! I have been using the same API namespacing approach in your routes and active model
Share
Share
Share
Share
Share
Share
Share
Share
Share
8/11/2014 How to integrate AngularJS with Rails 4 - Blog - Shelly Cloud
https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4 7/11
Reply
fantastic article! I have been using the same API namespacing approach in your routes and active model
serializers, but I have been using yeoman to compile to the views/application folder. What are your
thoughts on yeoman?

1
Reply
Micha Kwiatkowski 10 months ago Nick Shook
Thanks Nick!
Getting out of asset pipeline and rails templates is another way to go, and have its own set of
advantages and disadvantages. It's certainly easier to manage javascript dependencies with
yeoman and the workflow is more polished and optimized to frontend development (while Rails still
puts more focus on the backend).
OTOH if you want to use AngularJS as part of a bigger web application (so not SPA in a strict
sense) I believe doing it the way I described is the way to go. It's certainly easier to grasp to a Rails
developer that's still learning frontend technologies.

1
Reply
barillax 7 months ago Micha Kwiatkowski
Mentioned this above in another reply, but curious what you think of our approach to the
problem - replacing the asset pipeline entirely with Grunt, and managing front-end
dependencies with Bower. Our blog post about it: http://bit.ly/KzNoMw
It survived an upgrade from Rails 3 to 4 without any issues, so it seems reasonably
decoupled. Then again, we're not using many gems other than Devise that manage front-
end views, so it wasn't a big deal for us to give up niceties like Rails view helpers.


Reply
Micha Kwiatkowski 7 months ago barillax
Your approach certainly makes sense and you raised some very good points.
Ultimately it comes down to what gives you more value: grunt's focus on JS support
or your existing Rails code.
If you write most of your application in Javascript using Rails only as an API I'd go
with your solution. I found that my solution works best when integrating AngularJS
with an existing Rails project.
Worth mentioning is the new kid on the block: https://rails-assets.org/ being a middle
ground between those two alternatives.


Reply
Nick Shook 10 months ago Micha Kwiatkowski
I agree. fwiw, another point that my friend made why your approach is preferred is that a
page refresh every now and then is good for handling sessions. I def am not too happy with
how you handle 401 requests from a pure angular front-end.


Reply
nXqd 9 months ago
I am sorry but this is not a fully angularjs integrated. The route is served by rails which means you will make
an http request instead of xmlhttp request. And it completely ruins the idea of single page application.


Micha Kwiatkowski 9 months ago nXqd
Thank you for your feedback.
I have to disagree though, because it really depends. SPA is a new concept and there is no "one
true way", if there ever will be one. While you may contain your whole app within a single HTML
page, I also see in the wild a hybrid approach: having multiple pages and small SPAs contained on
Share
Share
Share
Share
Share
Share
8/11/2014 How to integrate AngularJS with Rails 4 - Blog - Shelly Cloud
https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4 8/11
Reply
page, I also see in the wild a hybrid approach: having multiple pages and small SPAs contained on
some of them.
You may imagine a blog application where posts management area is a SPA while the rest of the
pages are non-SPA. It's certainly easier to introduce AngularJS to an existing project this way.
One of the benefits of that for my todo app was that I was able to use devise for authentication, a
gem that most Rails programmers also already know. This way I could focus on implementing the
core functionality in AngularJS, and leave the boring parts in Rails. I believe it was a pragmatic
choice, and a right one for learning purposes.
I encourage you to check out the demo, because I don't think your argument about routes is valid.
Rails recognizes my SPA routes, but doesn't handle them: it's all done in AngularJS. Once you're in
SPA no page reload happens unless you step outside (e.g. when logging out).


Reply
nXqd 8 months ago Micha Kwiatkowski
Thanks for your reply.
I agree your idea on there is no "one true way" to implement a SPA. But still, I think it always
better to point this out in the tutorial so novice programmers won't get confused.
Btw, thanks for the nice post :)


Reply
Nexar 3 months ago
Hi I'm at a slightly different stage i.e. I am still trying to decide whether using Rails as the back end for an
Angular app is the right way to go. Please would you be able to comment or point me to any discussions
you are aware of regarding this.
Thanks


Reply
Micha Kwiatkowski 3 months ago Nexar
It depends on what you want from your backend and how comfortable you are with a given
technology. If it's just going to be a simple JSON API you can't go wrong with Sinatra or even
something like Firebase (see http://angularfire.com/ ). If you need something more, then Rails may
be the way to go.


Reply
boriscy 4 months ago
Thanks this post solved my issue working with angular and turbolinks.


Reply
Max 5 months ago
In your example above you run
Task(taskListId).all()
how come I have to run
new Task(taskListId).all()
calling Task().all() giving has no method 'all'


winescout 5 months ago
Nice Post, thanks. I know I'm late to party, but wanted to bring up the Angular Service you have written. If
I'm reading it correctly, you will get the raw attrs, and not a $resource instance back from your create
Share
Share
Share
Share
Share
Share
8/11/2014 How to integrate AngularJS with Rails 4 - Blog - Shelly Cloud
https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4 9/11
Reply
I'm reading it correctly, you will get the raw attrs, and not a $resource instance back from your create
function, and thus will not be able to call update on it without first instantiating a new instance, passing the
attrs back in.
create: (attrs) ->
new @service(task: attrs).$save (task) ->
attrs.id = task.id
attrs
Why not just return the promise you get back from $save, like so?
create: (attrs) ->
new @service(task: attrs).$save()


Reply
indykish 9 months ago
Dear,
I see that you have this in your application.js. Have you manually downloaded angular.*.js files into
lib/assets dir ?
//= require lib/angular.min
//= require lib/angular-resource.min


Reply
Micha Kwiatkowski 9 months ago indykish
Yes, section "Including AngularJS files" contains details on how to make that work.


Reply
Guest 9 months ago
Very nice article, but I don't think that we really need turbo links in this case. We can handle by using
angular router :)


Reply
Micha Kwiatkowski 9 months ago Guest
Thanks!
Tubrolinks workaround may come in handy when you need to support different SPAs on multiple
pages, just as I described in the post. :)

1
Reply
Fred 9 months ago
Sweet! This is a quick and easy read on integrating Angular with a Rails API. Glad I stumbled across it.
Thanks for sharing.


Reply
wwwoodall 10 months ago
Short and sweet. Thanks!


Reply
Pedro 10 months ago
This is great! thank you so much for sharing.


Christian 10 months ago
Nice article, I just want to mention two things:
* If you use a namespace for your api, you probably want to add a version too, like /api/v1
Share
Share
Share
Share
Share
Share
Share
Share
8/11/2014 How to integrate AngularJS with Rails 4 - Blog - Shelly Cloud
https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4 10/11
Reply
* If you use a namespace for your api, you probably want to add a version too, like /api/v1
* You should not respond with an Array as the JSON root object in the response, as a CSRF attack could
overwrite the Array constructor in JS and intercept all server responses. Always use an object iteral as root
object.


Reply
Micha Kwiatkowski 10 months ago Christian
Christian, thank you for reading!
Yeah, good point about the URL. Versioning APIs is another can of worms, so didn't want to
muddle the topic with that.
Abusing Array constructor is an interesting attack, thanks for pointing that out! I'm gonna learn
more about this and update the code and the article at some point.


Reply
Mike Bard 10 months ago Micha Kwiatkowski
Michal, I see some changes in repo. Have you free time to update post? )


Reply
Micha Kwiatkowski 10 months ago Mike Bard
Hey Mike.
I wanted for this tutorial to be general in nature. Although I use examples from the
todo project so that the text is more concrete and understandable, their specifics are
not important here - the lessons learned are.
So I won't be updating the code here. I feel it would unnecessarily muddle the focus
of the article.
I think the new code in the repo, especially the last pull request, is a good example of
how a basic application like this may evolve into something bigger. You are free to
ask questions about the design decisions I made: here or on github, whatever you
prefer.


Subscribe Add Disqus to your site
Share
Share
Share
Share
Subscribe RSS
Follow on Twitter
Follow on Google+
8/11/2014 How to integrate AngularJS with Rails 4 - Blog - Shelly Cloud
https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4 11/11
Get most interesting Ruby and DevOps articles curated by our team delivered to your email once a month.
Your email
Join our newsletter
Recent posts
28 JulNew: Shelly Cloud status feed
10 AprHeartbleed vulnerability
21 FebHow you can ruin your business by choosing self-managed hosting
17 FebHow to create a "Follow Us" pop-up box using the ngAnimate library
Company
About Us
Privacy Policy
Terms of Service
Platform
Support
Documentation
Pricing
Pricing comparison
Own servers comparison
Status
Community
Blog
Twitter
Google+
Facebook
Account
Sign up
Log in
Shelly Cloud
2014 Shelly Cloud
This site uses cookies. By continuing to browse the site, you are agreeing to our use of cookies. Review our Privacy Policy
for more details.
Talk to a human

You might also like