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.

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

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.

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:

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:

renderjson: 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 ArraySerializerActiveModel::ArraySerializer.root=falseend

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:

describeApi::TasksControllerdoit"should be able to create a new record"dopost:create,task_list_id: task_list.id,task: {description: "New task"},format: :jsonresponse.shouldbe_successJSON.parse(response.body).should=={'id'=>123,...}endend

An important detail to note here is the use of format: :json. This makes sure
that the parameters are passed and interpreted as JSON.

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:

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:

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:

With that in mind, the main application module is defined in todoApp.js.coffee
and looks like this:

todoApp=angular.module('todoApp',['ngResource'])

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.

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
embedded 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):

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:

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:

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.