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.
- Installing phpcs on a Mac ft. VSCode & WordPress, The Really Simple Guide - May 8, 2021
- How To Set Up Your WordPress Development Environment with a Large Database ft. MAMP & Mac, The Really Simple Guide - April 24, 2020
- Next.js ▲ + Typescript + Storybook The Really Simple Guide 2019 - November 25, 2019