AngularJS on Rails

AngularJS on Rails

AngularJS is an extremely popular JavaScript library that enables you to quickly and easily create rich web applications. In this article we will show you how to integrate AngularJS with your Rails app. We will also build a simple AngularJS application called VisitorsCenter. The VisitorsCenter application allows the user to track visitors that are coming and going from a building such as an office building.

Setup Rails Application
Before we begin, we will need to add a couple gems to our Gemfile. The angularJs-rails gem provides integration with the AngularJS library and our Rails application. The bootstrap-sass gem merely adds bootstrap support so we can focus on the code rather than the style of the app. Add these gems to your gemfile now as listed below.

Gemfile
1
2
gem 'angularjs-rails', '~> 1.2.25'
gem 'bootstrap-sass', '~> 3.2.0.2'

Now run a bundle install to install the gems:

1
bundle install

Next, we need to create a model called Visitor. The Visitor model will represent a visitor that visits. Run the command below to create the visitor model now:

1
2
rails g model Visitor first_name:string last_name:string reason:string
rake db:migrate

Great, now we need to create a Visitors controller that will give us a way to interact with our model. The Visitors controller will have 3 different actions in this example application. The first action, index will return either the visitors page or a json list of visitors depending on how it is accessed. The second action, create will be responsible for creating the visitor. The final action, destroy will destroy the visitor. Run the command below to create this controller now:

1
rails g controller Visitors index create destroy

Now let’s modify our routes file to set up the proper paths and add a site root. Open up your routes file and modify it so that it looks like the code listed below:

routes.rb
1
2
3
4
Rails.application.routes.draw do
  resources :visitors, only: [:index, :create, :destroy], defaults: {format: :json}
  root to: "visitors#index"
end

The code fragment that says defaults: {format: :json} tells Rails that we wish to return json by default for our actions. We do this because most of the interaction in our application will be via JSON.

By default, AngularJS knows nothing of the cross site request forgery (CSRF) protections in our applications. We need a way to tell AngularJS how to interact with our application while obeying the CSRF protections that we have in place. Luckily we have a way to do this. Open up your ApplicationController and add in the code listed below.

If you are using Rails 4.2 and up, use the code below:

application_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  after_action :set_csrf_cookie_for_ng

  def set_csrf_cookie_for_ng
    cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
  end

  protected
  def verified_request?
    super || valid_authenticity_token?(session, request.headers['X-XSRF-TOKEN'])
  end
end

If you are still using Rails 4.1, use the code below instead:

application_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  after_action :set_csrf_cookie_for_ng

  def set_csrf_cookie_for_ng
    cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
  end

  protected
  def verified_request?
    super || form_authenticity_token == request.headers['X-XSRF-TOKEN']
  end
end

The code listed above will create a cookie called XSRF-TOKEN that will contain our form_authenticity_token. Any time a request is made, AngularJS will present that token in the HTTP headers for the request.

Now let’s modify our VisitorsController to allow for access to the Visitor model. Open up your VisitorsController and modify it so that it looks like the code listed below:

visitors_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class VisitorsController < ApplicationController
  respond_to :json

  def index
    respond_to do |format|
      format.json { render json: Visitor.all }
      format.html
    end
  end

  def create
    respond_with Visitor.create(visitor_params)
  end

  def destroy
    respond_with Visitor.destroy(params[:id])
  end

  private
  def visitor_params
    params.require(:visitor).permit(:first_name, :last_name, :reason)
  end
end

The code above is typical Rails code, with the exception being that we return JSON as a result. Since our application will be communicating primarily via AJAX we have no need for HTML other than the index action, which will return either html or json depending on the request type.

Next we need to add support for both AngularJS and Bootstrap to our application.js file. Open up your application.js file and modify it so that it looks like the code listed below.

application.js
1
2
3
4
5
6
7
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require angular
//= require angular-resource
//= require bootstrap-sprockets
//= require_tree .

In the code above we add support for AngularJS as well as Bootstrap. We also add support for a library called angular-resource which allows us to easily talk to our Rails application.

Now let’s add a bit of CSS for bootstrap. Create a new file called bootstrap_config.scss and add in the code listed below:

bootstrap_config.scss
1
2
@import "bootstrap-sprockets";
@import "bootstrap";

The next thing we need to do is create our AngularJS application. AngularJS applications typically consists of JavaScript code that glues together various bits of HTML. To get started doing this, the first thing we must do is rename our visitors.js.coffee file to visitors.js and modify it so that it looks like the code listed below. You can also rewrite this in CoffeeScript, but I use JavaScript for those that haven’t yet learned CoffeeScript.

visitors.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var visitorCenter = angular.module('VisitorCenter', ['ngResource']);

visitorCenter.factory("Visitor", function($resource) {
  return $resource("visitors/:id", { id: '@id' }, {
    index:   { method: 'GET', isArray: true, responseType: 'json' },
    update:  { method: 'PUT', responseType: 'json' }
  });
})

visitorCenter.controller("visitorsController", function($scope, Visitor) {
  $scope.visitors = Visitor.index()

  $scope.addVisitor = function() {
    visitor = Visitor.save($scope.newVisitor)

    $scope.visitors.push(visitor)
    $scope.newVisitor = {}
  }

  $scope.deleteVisitor = function(index) {

    visitor = $scope.visitors[index]
    Visitor.delete(visitor)
    $scope.visitors.splice(index, 1);
  }
})

There is a lot going on here, so i’m going to break it down into pieces. The first line:

1
var visitorCenter = angular.module('VisitorCenter', ['ngResource']);

defines an AngularJS module. AngularJS modules can be thought of as individual components in your application. You’ll notice we include ngResource as an argument. ngResource provides easy access to RESTful resources such as our Rails application.

The next set of lines:

1
2
3
4
5
6
visitorCenter.factory("Visitor", function($resource) {
  return $resource("visitors/:id", { id: '@id' }, {
    index:   { method: 'GET', isArray: true, responseType: 'json' },
    update:  { method: 'PUT', responseType: 'json' }
  });
})

defines a service, in this case, it ties in the ngResource service mentioned earlier and tells AngularJS how to talk to our application.

The next set of lines:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
visitorCenter.controller("visitorsController", function($scope, Visitor) {
  $scope.visitors = Visitor.index()

  $scope.addVisitor = function() {
    visitor = Visitor.save($scope.newVisitor)

    $scope.visitors.push(visitor)
    $scope.newVisitor = {}
  }

  $scope.deleteVisitor = function(index) {

    visitor = $scope.visitors[index]
    Visitor.delete(visitor)
    $scope.visitors.splice(index, 1);
  }
})

define a controller. Controllers tell AngularJS how to interact with our application similar to how Rails controllers are used to tell Rails how our views interact with our models.

ow that we’ve written the JavaScript application, we need to create our view to tie everything together. Open up the index view for your Visitors controller and modify it so that it looks like the code listed below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<div class="container" ng-app="VisitorCenter">
  <h1>Visitors</h1>

  <div ng-controller="visitorsController">
    <div class="well">
      <h3>Add a new Visitor</h3>
      <form ng-submit="addVisitor()">
        <div class="row">
          <div class="col-xs-6">
            <input type="text" ng-model="newVisitor.first_name" class="form-control" placeholder="First Name" />
          </div>
          <div class="col-xs-6">
            <input type="text" ng-model="newVisitor.last_name" class="form-control" placeholder="Last Name" />
          </div>
        </div>
        <div class="row">
          <div class="col-xs-12">
            <br />
            <input type="text" ng-model="newVisitor.reason" class="form-control" placeholder="Reason for Visit" />
          </div>
        </div>
        <div class="row">
          <div class="col-xs-12 text-center">
            <br />
            <input type="Submit" value="Add Visitor" class="btn btn-primary" />
          </div>
        </div>
      </form>
    </div>

    <h3>Currently Visiting</h3>
    <hr />
    <table class="table table-bordered table-striped">
      <thead>
        <tr>
          <th>First Name</th>
          <th>Last Name</th>
          <th>Reason for Visit</th>
          <th> </th>
        </tr>
      </thead>
      <tbody>
        <tr ng-show="!visitors.length">
          <td colspan="4">No visitors in the building.</td>
        </tr>
        <tr ng-repeat="visitor in visitors">
          <td>{{ visitor.first_name }}</td>
          <td>{{ visitor.last_name }}</td>
          <td>{{ visitor.reason }}</td>
          <td><a class="btn btn-danger" ng-click="deleteVisitor($index)">Remove</a></td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

Let’s break this down a bit:

1
2
3
<div class="container" ng-app="VisitorCenter">
  ...
</div>

The outer div on the first line has an attribute called ng-app. The ng-app attribute tells AngularJS that this is part of our AngularJS application. In this case we specify the name of our AngularJS module, VisitorCenter.

1
2
3
<div ng-controller="visitorsController">
  ...
</div>

The next inner div contains an attribute called ng-controller. This attribute tells AngularJS that we wish to use our visitorsController as the controller for this portion of the application.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<form ng-submit="addVisitor()">
  <div class="row">
    <div class="col-xs-6">
      <input type="text" ng-model="newVisitor.first_name" class="form-control" placeholder="First Name" />
    </div>
    <div class="col-xs-6">
      <input type="text" ng-model="newVisitor.last_name" class="form-control" placeholder="Last Name" />
    </div>
  </div>
  <div class="row">
    <div class="col-xs-12">
      <br />
      <input type="text" ng-model="newVisitor.reason" class="form-control" placeholder="Reason for Visit" />
    </div>
  </div>
  <div class="row">
    <div class="col-xs-12 text-center">
      <br />
      <input type="Submit" value="Add Visitor" class="btn btn-primary" />
    </div>
  </div>
</form>

The ng-submit attribute on our form tells AngularJS that we wish to use the addVisitor() method on our controller to process the form request. Each of the input elements contain an ng-model attribute. This attribute maps the input elements to our model.

1
2
3
<tr ng-show="!visitors.length">
  <td colspan="4">No visitors in the building.</td>
</tr>

The ng-show attribute on the first row tells AngularJS that we only want to show the row if the condition mentioned is matched. In this case we only want to show the first row if there are no visitors.

1
2
3
<tr ng-repeat="visitor in visitors">
  ...
</tr>

The ng-repeat attribute is a loop. This particular loop tells AngularJS that we want to loop through each visitor.

1
2
3
<td>{{ visitor.first_name }}</td>
<td>{{ visitor.last_name }}</td>
<td>{{ visitor.reason }}</td>

Text contained within {{ .... }} are AngularJS expressions. In this case we are telling AngularJS to render the fields mentioned in each expression.

1
<td><a class="btn btn-danger" ng-click="deleteVisitor($index)">Remove</a></td>

The ng-click button tells AngularJS to run the specified controller function when the html tag in question is clicked. In this case we run the code to delete the specified user.

So far so good, That’s it!!! for this introduction to AngularJS and Rails. See ya!!! :)