Using Angular With Rails ==================== In one of the Rails application we decided to use **AngularJS** as front-end and **ROR (RubyOnRails)** as back-end ([Why Angular?](http://www.sitepoint.com/10-reasons-use-angularjs/)) , I faced some problem in integrating Angular with Rails and I think most of the developers face issues in integration, because currently there is no **Rails Way** for managing the front-end assets needed to create a [rich JavaScript](http://en.wikipedia.org/wiki/List_of_rich_Internet_application_frameworks) application using AngularJS. Rails wasn't designed with a JavaScript framework like AngularJS in mind. Rails view is good enough to serve up pages. Rich Javascript-based single-page applications are not something Rails encourages. In this Tutorial i am going to show you how to integrate Angular with Rails and how they work together. > **Note:** I am assuming readers have basic knowledge of ROR and AngularJS, If you're completely new, you may not be able to understand some parts, but you should still be able to follow along as i will show a demo app here. Goal ------ Create an application which will use *AngularJS* In front-end and Rails In Back-end. Main Features -------------------- - **Add Product:** You can add a new product. - **List Products:** You can see the list of products. - **Show Product:** You can view the full details of the product. - **Edit Product:** You can edit a particular product on click. - **Delete product:** You would be able to delete product. Tools ------- - Ruby ~> 2.0 or later - Rails ~> 4.0.x - Postgresql or MySQL Project Setup -------------- ```sh $ rails new rails_with_angular -d=postgresql -T ``` > **Tip:** Remove "-d=postgresql" option if you want to use default sqlite database and "-T" option is for tests generated by Rails. Open your Gemfile and add these lines ```sh gem 'angularjs-rails' gem 'twitter-bootstrap-rails' ``` > **Tip:** You can also [download](https://angularjs.org/) and use AngularJS library by putting in Rails assets folder if you don't wish to use angularjs-rails gem. Install Gems and create database ```sh $ bundle install $ rake db:create ``` After running ``bundle install`` run the generator: ```sh $ rails generate bootstrap:install static ``` Start the Rails server ```sh $ rails s ``` Go to **[localhost:3000](localhost:3000)** and you will find Rails welcome page "welcome to Rails" Create a home page. ```sh $ rails generate controller home index ``` Change the default Rails welcome page to our homepage. config/routes.rb ```sh # Add the following line at bottom of routes.rb root 'home#index' ``` Generate a **CRUD**(*create, read, update and destroy*) for product ```sh $ rails generate scaffold Product name:string price:decimal description:text image_url:string $ rake db:migrate ``` Include Angular Libraries ------------------------ Now we want to tell our application to include the AngularJS files, and we want to make sure it gets loaded before other files that depends on it. > **Note:** Note: Angular and Turbolinks can conflict with one another, so you can disable Turbolinks if any issue arises. ```sh // Add the following lines //= require angular //= require angular-route //= require angular-resource ``` After adding required files the application.js file should looks like this ``` //= require angular //= require angular-route //= require angular-resource //= require jquery //= require jquery_ujs //= require twitter/bootstrap //= require turbolinks //= require_tree . ``` Setting up the layout ---------------------- We'll add ng-app and ng-view tag in our layout, which signals that we have an Angular app in our page. #### Create a new layout file for angular ```sh <%= content_for?(:title) ? yield(:title) : "AngularWithRails" %> <%= csrf_meta_tags %> <%= stylesheet_link_tag "application", :media => "all" %> <%= javascript_include_tag "application" %>
<%= bootstrap_flash %>
<%= javascript_include_tag "application" %> ``` > **Note:** You can also use "application.html.erb". Just to make it separate i have created a new layout for Angular. Since we have created a new layout for angular, we have to tell the controller to use angular layout by adding angular layout in HomeController ```sh class HomeController < ApplicationController respond_to :html layout "angular" def index end end ``` When the very first time we request to our server then the request would be handled by Rails HomeController's index action and because we have set our app root to '*home#index*' path, the index action will finally respond to angular layout and inside the angular layout we have two main tags of Angular that is `ng-app` and `ng-view` . **ng-app** initialize the angular application and this allows Angular to handle the routing instead of Rails. **ng-view** is where angular will dump it's templates. Set CSRF cookie for ng ----------------------- Since only JavaScript that runs on your domain could read the cookie, your server can be assured that the XHR came from JavaScript running on your domain. To take advantage of this (CSRF Protection), your server needs to set a token in a JavaScript readable session cookie called XSRF-TOKEN on first HTTP GET request. On subsequent non-GET requests the server can verify that the cookie matches X-XSRF-TOKEN HTTP header ```sh class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception after_filter :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 ``` For details go to AngularJS official [website](https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection) Creating Angular files ---------------------------- Now it's time to work on front-end side. Let's create a directory for our angular routes, controllers and resources. You can name it whatever you want. ```sh $ mkdir -p app/assets/javascripts/angular ``` Now let's create the route, controller and resources. I'm calling controller as "products controller," and the convention in Angular is to append your controller filenames with Ctrl. Thus our filename will be app/assets/javascripts/angular/productsCtrl.js.coffee, I am putting all the routes inside the app/assets/javascripts/angular/app.js and all the Factory in app/assets/javascripts/angular/resources.js *But you can name it whatever you want* Creating Angular routes ---------------------------- First we'll add a routing directive in order to make our products#index to be our "default page." Here I'm defining my routing in app/assets/javascripts/angular/app.js. but again I don't think the filename matters. ```sh var myApp = angular.module('RailsWithAngular', ['ngRoute', 'ngResource']); //Routes myApp.config([ '$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) { $routeProvider.when('/products', { templateUrl: '/template/products/index.html', controller: 'ProductListCtr' }); $routeProvider.when('/products/new', { templateUrl: '/template/products/new.html', controller: 'ProductAddCtr' }); $routeProvider.when('/products/:id/edit', { templateUrl: '/template/products/edit.html', controller: "ProductUpdateCtr" }); $routeProvider.when('/products/:id', { templateUrl: '/template/products/show.html', controller: "ProductShowCtr" }); $routeProvider.otherwise({ redirectTo: '/products' }); } ]); ``` Creating product controller ---------------------------- Now we will create a new file productsCtrl.js." Here I'm going to put all the crud action, just think same as rails. ```sh //Controller myApp.controller("ProductListCtr", ['$scope', '$http', '$resource', 'Products', 'Product', '$location', function ($scope, $http, $resource, Products, Product, $location) { $scope.products = Products.query(); $scope.deleteProduct = function (ProductId) { if (confirm("Are you sure you want to delete this Product?")) { Product.delete({ id: ProductId }, function () { $scope.products = Products.query(); $location.path('/'); }); } }; }]); myApp.controller("ProductUpdateCtr", ['$scope', '$resource', 'Product', '$location', '$routeParams', function ($scope, $resource, Product, $location, $routeParams) { $scope.product = Product.get({id: $routeParams.id}); $scope.update = function () { if ($scope.productForm.$valid) { Product.update({id: $scope.product.id}, {product: $scope.product}, function () { $location.path('/'); }, function (error) { console.log(error) }); } }; }]); myApp.controller("ProductShowCtr", ['$scope', '$resource', 'Product', '$location', '$routeParams', function ($scope, $resource, Product, $location, $routeParams) { $scope.product = Product.get({id: $routeParams.id}); }]); myApp.controller("ProductAddCtr", ['$scope', '$resource', 'Products', '$location', function ($scope, $resource, Products, $location) { $scope.product = {name: '', price: '', description: '', image_url: '' }; $scope.save = function () { if ($scope.productForm.$valid) { Products.create({product: $scope.product}, function () { $location.path('/'); }, function (error) { console.log(error) }); } } }]); ``` Create Resources ---------------------------- A factory which creates a resource object that lets you interact with RESTful server-side data sources. The returned resource object has action methods which provide high-level behaviors without the need to interact with the low level $http service. Requires the ngResource module. You can read more about Resource [here](https://docs.angularjs.org/api/ngResource/service/$resource) ```sh //Factory myApp.factory('Products', ['$resource', function ($resource) { return $resource('/products.json', {}, { query: { method: 'GET', isArray: true }, create: { method: 'POST' } }) }]); myApp.factory('Product', ['$resource', function ($resource) { return $resource('/products/:id.json', {}, { show: { method: 'GET' }, update: { method: 'PUT', params: {id: '@id'} }, delete: { method: 'DELETE', params: {id: '@id'} } }); }]); ``` Add Angular template folder ---------------------------------- We'll also want a place to keep our Angular templates. I decided to put mine in public/template. Again, you can place them wherever you like. ```sh mkdir public/template ``` I am going to follow something like rails directory structure, so i will be creating a separate folder for products, Thus our products folder path will be public/template/products ```sh mkdir -p public/template/products ``` index.html ```sh
Name Price Description Action
No Product found, Please create one.
{{product.name}} {{product.price}} {{product.description}} |
``` _form.html ```sh

Name is required.


Price is required.




Back
``` new.html ```sh

Add a new product

``` edit.html ```sh

Edit product

``` show.html ```sh
Product Image

{{product.name}}

Price: {{product.price}} $

{{product.description}}

Back Edit

``` Don't forget to run your server ```rails s``` Now, if you go to http://localhost:3000/ you should see the products index page. After following this whole article, you should be able to add new product, edit product, delete product and show product. You can improve your application by implementing authentication and putting some more logics. >Fork working code from [Github](https://github.com/ahmadhasankhan/RailsWithAngular) > Visit The Running [Application](https://railswithangular.herokuapp.com/)