10 Easy Steps to Connect Rails with AngularJS

Probably one of many advantages of working with Rails, is the fact that is an end to end framework, meaning, you can get pretty much everything done (back-end and front-end) with rails alone, but is not necessarily the only way to do it, rails is a powerful framework and it can be utilized for backend purposes only, leaving room for others frameworks such as Angular JS to tackle the client side work.

In this guide, I will show you how to connect rails with angular in 10 simple steps, I will use a blog app as a reference and will assume you are familiar with Angular and Rails basic concepts, so let’s get to it!

1. Initial Setup

Let’s create a new rails app by typing in our terminal window:

rails new myapp
  • Remove turbolinks gem from the Gemfile (turbolinks and Angular hate each other):
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks'

Then remove turbolinks from your myapp/app/views/layouts/application.html.erb file.

<!DOCTYPE html>
<html>
  <head>
    <title>Myapp</title>
    <%= csrf_meta_tags %>
     <!-- ######## REMOVE TURBOLINKS FROM HERE ######## -->
    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
     <!-- ######## REMOVE TURBOLINKS FROM HERE ######## -->
  </head>
  <body>
    <%= yield %>
  </body>
</html>

Leaving it like this:

<!DOCTYPE html>
<html>
  <head>
    <title>Myapp</title>
    <%= csrf_meta_tags %>
    <%= stylesheet_link_tag    'application', media: 'all' %>
    <%= javascript_include_tag 'application' %>
  </head>
  <body>
    <%= yield %>
  </body>
</html>
  • Add the following gems to your Gemfile:
# *** Gems for Angular *** #

# Respond to ActiveRecord errors in JSON format
gem 'responders'
# Convert ruby database in JSON format
gem 'active_model_serializers'
# Manage frontend assets
gem 'bower-rails'
# Allows to write HTML code within js assets folder
# and compile HTML into assets pipeline
gem 'angular-rails-templates'

Then run,

bundle install
  •  Now Initialize a json file for bower-rails to get started up,  just type in your terminal:
rails g bower_rails:initialize json

This creates an initializer in within our config for bower rails:

config/initializers/bower_rails.rb

BowerRails.configure do |bower_rails|
  # Tell bower-rails what path should be considered as root. Defaults to Dir.pwd
  # bower_rails.root_path = Dir.pwd

  # Invokes rake bower:install before precompilation. Defaults to false
  # bower_rails.install_before_precompile = true

  # Invokes rake bower:resolve before precompilation. Defaults to false
  # bower_rails.resolve_before_precompile = true

  # Invokes rake bower:clean before precompilation. Defaults to false
  # bower_rails.clean_before_precompile = true

  # Invokes rake bower:install:deployment instead of rake bower:install. Defaults to false
  # bower_rails.use_bower_install_deployment = true
  #
  # Invokes rake bower:install and rake bower:install:deployment with -F (force) flag. Defaults to false
  # bower_rails.force_install = true

  # Change the default directory name
  # bower_rails.bower_components_directory = 'bower_components'
end
  •  Add dependencies to your bower.json file:

I am going to use “Angular v1.5.8”, “angular-ui-router” and “bootstrap”, feel free to add as many dependencies as you need, I decided to add bootstrap for this project to have some HTML styling out of the box.

├── bin
│   ├── bundle
│   ├── rails
│   ├── rake
│   ├── setup
│   ├── spring
│   └── update
├── bower.json <-
├── config

My final bower.json file looks like this:

{ 
    "vendor": {
        "name": "bower-rails generated vendor assests",
        "dependencies": {
            "angular": "v1.5.8",
            "angular-ui-router": "latest",
            "bootstrap": "~3.0.0"
        }
    }
}

Now run:

rake bower:install
  • Add dependencies to your asset pipeline:

app/assets/stylesheets/application.css

/*
 *= require bootstrap/dist/css/bootstrap
 */

app/assets/javascript/application.js

//= require bootstrap/dist/js/bootstrap
//= require angular
//= require angular-ui-router
//= require angular-rails-templates

 2. Create Models, Run Migrations

By using rails generators we can easily both create a post model and a corresponding migration by typing:

rails g model post

Let’s add “content” to our migration, it will look something like this:

myapp/db/migrate/20161019000053_create_posts.rb

class CreatePosts < ActiveRecord::Migration[5.0]
  def change
    create_table :posts do |t|
      t.text :content
      t.timestamps
    end
  end
end

Now run

rake db:migrate

Your schema file should look like this now:

myapp/db/schema.rb

ActiveRecord::Schema.define(version: 20161019000053) do

  create_table "posts", force: :cascade do |t|
    t.text     "content"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

end

3. Serializers

In order to allow communication between your front and backend you need to serialize your data; if you need to know more about using serializers, please read my blog post about how to connect your rails backend to your javascript frontend.

Let’s create a post serializer:

  • Create  app/serializers directory.
  • Create a serializer file app/serializers/post_serializer.rb
class PostSerializer < ActiveModel::Serializer
    attributes :id, :content
end

4. Routes

Add a root route and any needed routes for your models:

Rails.application.routes.draw do
    root 'application#index'
    resources :post, only: [:index, :show, :create, :update, :destroy]
end

Note: Check if routes are working

rake routes

5. Setup Controllers

Application Controller

Set up your backend to respond to CSRF in your application controller.

Cross-Site Request Forgery (CSRF) is a type of attack that occurs when a malicious web site, email, blog, instant message, or program causes a user’s web browser to perform an unwanted action on a trusted site for which the user is currently authenticated. – OWASP

  • Set CSRF cookies. (authentication token)

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
 protect_from_forgery with: :exception
    
 after_filter :set_csrf_cookie
 respond_to :json
    
 # Allows cookies with csrf token to post delete update
 def set_csrf_cookie
   cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
 end
    
 def index 
 end
    
 protected
  def verified_request?
    super || valid_authenticity_token?(session, request.headers['X-XSRF-TOKEN'])
  end
end

Post Controller

app/controllers/post_controller.rb

class  PostController < ApplicationController

 def index
   posts = Post.all
   render json: posts
 end
    
 def create
   post = Post.new(post_params)
   if post.save
    render json: post
   else
    render json: { errors: post.errors.full_messages }, status: :unprocessable_entity
   end
 end
    
 def show
   post = Post.find(params[:id])
   render json: post
 end
    
 def update
   post = Post.find(params[:id])
   if post.update
    render json: post
   else
    render json: { errors: post.errors.full_messages }, status: :unprocessable_entity
   end
 end
    
 def update
   post = Post.find(params[:id])
   if post.update(post_params)
    render json: post
   else
    render json: post
   end
 end
    
 def destroy
   post = Post.find(params[:id])
   render json: post
 end
    
 private    
    def post_params
      params.require(:post).permit(:content)
    end
end

6. Update Application Views

We need to let our application know that we are going to be using Angular to give super powers to our HTML, to do that we need to add the name of our angular app to our application.html.erb using a directive called ng-app, this app is going to be called “app”.

app/layouts/application.html.erb

<!DOCTYPE html>
<html ng-app="app"> <!--- ng-app goes here -->
<head>
  <title>RailsLove</title>
  <%= csrf_meta_tags %> 
  <%= stylesheet_link_tag 'application', media: 'all' %>
  <%= javascript_include_tag 'application' %>
</head>
<body>
  <%= yield %>
</body>
</html>

Since we added an index controller action for our root route, let’s go ahead and create that view, then we need to add <ui-view></ui-view> inside this view, this directive tells $state where to place your templates.

app/views/application/index.html.erb

<ui-view></ui-view>

7. Create Angular Application File & Routes

Application

First we need to create our app file:

myapp/app/assets/javascripts/app.js

Let’s create this app inside an IIFE* function and add ‘ui-router’ and ‘templates’ to our app dependencies, I am also adding a way to handle X-CSRF-Token errors:

(function() {
    'use strict';
    
    angular
        .module('app', ['ui.router', 'templates'])
        .config(function($httpProvider) {
        // for CSRF Errors
        $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content');
        });
}());

*IIFE : An immediately-invoked function expression (or IIFE, pronounced “iffy”) is a JavaScript design pattern which produces a lexical scope using JavaScript’s function scoping. Immediately-invoked function expressions can be used to avoid variable hoisting from within blocks, protect against polluting the global environment and simultaneously allow public access to methods while retaining privacy for variables defined within the function. – Wikipedia

If you are also wondering what is that ‘use-strict’ on the top of the file, here a short description:

Strict Mode is a new feature in ECMAScript 5 that allows you to place a program, or a function, in a “strict” operating context. This strict context prevents certain actions from being taken and throws more exceptions.

Strict mode helps out in a couple ways:

  • It catches some common coding bloopers, throwing exceptions.
  • It prevents, or throws errors, when relatively “unsafe” actions are taken (such as gaining access to the global object).
  • It disables features that are confusing or poorly thought out.

Routes

Now we can create a couple of basic routes (feel free to add as many as you need), by using $stateProvider and $urlRouterProvider provided by ui-router:

myapp/app/assets/javascripts/routes.js

(function() {
    'use strict';
    
    angular
        .module('app')
        .config(function($stateProvider, $urlRouterProvider) {
            $stateProvider
                .state('home', {
                    templateUrl: 'home/home.html',
                    controller: 'HomeController as vm'
                })
                .state('home.posts', {
                    templateUrl: 'posts/posts.html',
                    controller: 'PostsController as vm'
                })
            $urlRouterProvider.otherwise('/');
        });
}());

8. Create Views, Controllers & Services/Factories

Based on the routes we just created, we can start adding more files to our application, like controllers, views and factories. The following is a basic structure of how I prefer to build my angular applications:

myapp/app/assets/javascripts/

├── home
│   ├── home.controller.js
│   └── home.html
└── posts
    ├── posts.controller.js
    ├── posts.factory.js
    └── posts.html

Controllers

Home Controller

home.controller.js

(function() {
    'use strict';
    
    function HomeController() {
         var vm = this;


    };
    
    angular
        .module('app')
        .controller('HomeController', HomeController)
}());

Note: Go into rails console and create some posts

Posts Controller

posts.controller.js

(function() {
    'use strict';
    
    function PostsController() {
    
        var vm = this;
        
        // vm callable methods 
        vm.test = "Hello World"

        vm.getPosts = getPosts;
        vm.getPost = getPost;
        vm.createPost = createPost;
        vm.updatePost = updatePost;
        vm.deletePost = deletePost;
        
        // instatiaded functions
        activate();
            
        // defined methods
        function activate() {
            getPosts()
        }

        // INDEX
        function getPosts() {
            
        }
        
        // SHOW
        function getPost() {
        
        }
        
        // CREATE
        function createPost() {
        
        }
        
        // UPDATE
        function updatePost() {
        
        }
        
        // DELETE
        function deletePost() {
        
        }
    };
    
    angular
        .module('app')
        .controller('PostsController', PostsController);
}());

You probably noticed I called my controller “vm” and then assign “this” to that variable, I do this so I can use “vm” anywhere in the controller and there are no confusions as to what “this” could be.

Don’t mind all the empty methods, will talk about them in a little bit, but don’t they look like a rails controller? (Interesting right!). I assigned  vm.test = "Hello World"  we will be using it in just a minute to check if everything is working.

Views

Home View

home.html

I am going to create a couple of links using “ui-sref” and again ui-view” to render our nested views.

<a href="#" ui-sref="home">Home</a>
<a href="#" ui-sref="home.posts">Posts</a>
  <ui-view><!--partials will render here--></ui-view>

Posts View

posts.html

Now I will be instantiating vm.test and will give use to the ng-repeat directive, to display a list of all posts saved in our database and I’ll pass track by to keep all posts in the same order when any changes are made.

<h1>{{vm.test}}</h1> <!-- Should render 'Hello World' -->

<!-- this will render a list of posts -->
<ul> 
    <li ng-repeat="post in vm.posts | track by post.id">{{post.content}}</li>
</ul>

Run rails s and open up your browser, if everything is working you should see “Hello World!”.

You won’t see any posts simply because we haven’t connected our frontend with our database just yet. Let’s work on that now!

Factory (Model)

This is the connecting piece that accesses the back end and returns the information to the front end controller, and the controller will show the data in the view. I will be injecting $http into my PostFactory to be able to make HTTP Requests.

(function() {

    'use strict';

    // Model of the front end world

    function PostFactory($http) {
        return {
            getPosts:   getPosts,
            getPost:    getPost,
            createPost: createPost,
            updatePost: updatePost,
            deletePost: deletePost
        }
        
        // defined methods

        function getPosts() {
            return $http.get('/posts')
                        .then(handleResponse)
                        .catch(handleError)
        }
        
        function getPost(id) {
            return $http.get('/posts/' + id)
                        .then(hanleResponse)
                        .catch(hadndleError)
        }
        
        function createPost(post) {
            var req = {
                method: 'POST',
                url: '/posts',
                headers: {
                    'Content-Type': 'application/json'
                },
                data: {
                    post: post 
                }
            }
            return $http(req)
                        .catch(handleError)
        }
        
        function updatePost(post) {
             var req = {
                method: 'PUT',
                url: '/posts/' + post.id,
                headers: {
                    'Content-Type': 'application/json'
                },
                data: {
                    post: post 
                }
            }
            return $http(req)
                       .catch(handleError)
        }

        function deletePost(post) {
             var req = {
                method: 'DELETE',
                url: '/posts/' + post.id,
                headers: {
                    'Content-Type': 'application/json'
                },
                data: {
                    post: post 
                }
            }
            return $http(req)
                        .catch(handleError)
        
        }
        
        // handle $http responses
        
        function handleResponse(respose) {
            return response.data
        }
        
        function handleError(error) {
            console.log(error)
        }
    };
    
    angular
        .module('app')
        .factory('PostFactory', PostFactory);
}());

9. Connect All The Pieces Together

Now that PostFactory is completed, we inject it into our PostController, then we will call our factory method getPosts() and set our posts using a setPost() function like so:

post.controller.js

(function() {
    'use strict';
    
    function PostsController(PostFactory) { // <-
        
        var vm = this;
        
        // callable methods on the VM
        vm.test = "Hello World"
        vm.getPosts = getPosts; <--
        vm.getPost = getPost;
        vm.createPost = createPost;
        vm.updatePost = updatePost;
        vm.deletePost = deletePost;
        
        // instatiaded functions
        activate();
            
        // defined methods
        function activate() {
            getPosts()
        }


        function getPosts() {
            return PostFactory.getPosts() // <---
                        .then(setPosts)
        }
        
// ...    
        function setPosts(data) { // <--
            return vm.posts = data;
        }
//...

}());

As you can see in line 25, getposts() calls our PostFactory.getposts() function and passes the return data through our setPosts() function in line 31, returning vm.posts

Now, vm.posts contains an array of Post Objects, since we called our Postcontroller “vm” also in our routes file, our post.html should be rendering a list of all posts you created in your rails console.

Now you can access your factory from your controller since it was injected into it and then use the returned data to render it into your views. Connection made!

10. Add Complete Functionality

Up to this point we have structured our app to get our posts, but we created those posts in our console, we want the user to be able to create a post, here is how we do it.

Remember those controller empty functions from before? Using the same logic with did before when getting all posts, we call our factory functions to get, post, update or delete data.

Create a Post

I am a big believer of using partials, you don’t want to write all your HTML in just one file, using partials makes your code more organized and easy to read. Lucky for us Angular has a directive that makes using partials super easy. ng-include

Lets add a partial to our posts view.

posts/posts.html

 <!-- Here ng-include -->
<div ng-include="'posts/new.html'"> <--
  <!-- Partial will render here -->
</div>

...

<!-- this will render a list of posts -->
<ul> 
    <li ng-repeat="post in vm.posts | track by post.id">{{post.content}}</li>
</ul>

Just type in your terminal:

touch posts/new.html

So now we have this:

├── home
│   ├── home.controller.js
│   └── home.html
└── posts
    ├── new.html
    ├── posts.controller.js
    ├── posts.factory.js
    └── posts.html

posts/new.html

Here I am going to add a form that will call my controller function createPost() on submit, for that we use ng-submit and populate a new “ng-model” that we’ll call vm.newPost”, to populate this model content we simply chain that to our model as vm.newPost.content” in our text area like this:

<h1>Create Post</h1>

<form ng-submit="vm.createPost()">
    <label for="content">Content:</label>
    <textarea ng-model="vm.newPost.content"></textarea>
    <input type="submit" value="Create Post">
</form>

Now we update our controller’s createPost() function:

(function() {
    'use strict';
    
    function PostsController(PostFactory) { 
//...
        function createPost() { 
            return PostFactory.createPost(vm.newPost)
            // after creating a post get all posts again and update the page
                        .then(getPosts) 
        }
//...
}());

Now we can create posts too!! Great!

The same way we can call the rest of our factory functions through our controller getpost() , updatePost() and deletePost() functions and have complete app functionality.

In case you still have any doubts about how this app works, take a look at the following illustration.

logomakr_1uaq5h

 

You may also like