AngularJS và Rails một app đơn giản

Giới thiệu

AngularJS là một thư viện của JavaScript khá là phổ biến và dễ sử dụng trong việc tạo ra các ứng dụng web hiện nay, khả năng truy xuất dữ liệu khá nhanh, giúp người dùng ít tương tác với server side nhiều hơn bằng việc chỉ cần 1 lần request lên server chúng ta đã có 1 đống dữ liệu để chơi ở cline rồi. 😃). nói nôm na là thế thôi. Bài viết này mình sẽ kết hợp AngularJS và Rails để tạo 1 app đơn giản.

Tạo một app Rails

Tạo 1 app rails thì có lẽ mọi người cũng đã biết rồi nhỉ. rails new AngularRails chạy cái èo xong, chúng ta đã có 1 app rails. Tiếp theo chúng ta cần thêm 1 tý về Gems trong Gemfile để cung cấp thứ viện angularJS cho ứng dụng Rails của chúng ta.

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

Rồi sau đó chạy

bundle install

cũng èo 1 cái xong. Giờ thì chúng ta cần tạo 1 model để chơi, ví dụ như tạo Article model như thế này:

rails g model Article title_name:string author:string content:string
rake db:migrate

lại èo xong. Có model rồi giờ chúng ta tạo Articles Controller để chọc thằng model.

rails g controller Articles 

giờ chúng ta sẽ modify lại route file để thiết lập đúng path cho app. mở file routes trong folder config

Rails.application.routes.draw do
  resources :articles, only: [:index, :create, :destroy], defaults: {format: :json}
  root to: "articles#index"
end

ở đống code trên chúng ta có thể chú ý đến defaults: {format: :json} cái này sẽ nói với Rails rắng luôn trả về Json cho mọi action( index, create, update, destroy ...) của nó. Tại sao phải làm vậy? có thể nói vì hầu hết các tương tác ở đây angular của chúng ta đều làm việc với JSON.

Theo mặc định thì Angular không thế nhận biết được CSRF trong ứng dụng của chúng ta, nên chúng ta cần một cách nào đó để Angular có thể tương tác với ứng dụng tuân theo CSRF. Và có issue thì sẽ có solution, chúng ta sẽ vào ApplicationController và thêm vài dòng code như sau:

  • đoạn code này áp dụng cho Rails 4.2 trở lên:
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

  • đoạn code này áp dụng có Rails 4.1 trở xuống:
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

đoạn code trên sẽ tạo ra một cookies là XSRF-TOKEN sẽ chứa nội dung của form_authenticity_token. Mỗi request được tạo ra thì AngularJS sẽ chỉ định token này vào trong HTTP headers cho mỗi request.

Giờ thì chúng ra sẽ viết các action của Controller để chọc đến Model như sau:

class ArticlesController < ApplicationController
  def index
    respond_to do |format|
      format.json { render json: Article.all }
      format.html
    end
  end

  def create
    article = Article.new(article_params)
    if article.save
      respond_to do |format|
        format.json {render json: article}
      end
    else
      respond_to do |format|
        format.json {response_error("Error create")}
      end
    end
  end

  def destroy
    article = Article.find_by_id(params[:id])
    if visitor.destroy
      respond_to do |format|
        format.json {render json: article}
      end
    else
      respond_to do |format|
        format.json {response_error("Error destroy")}
      end
    end
  end

private
  def article_params
    params.require(:article).permit(:title_name, :author, :content)
  end
end

Tiếp theo chúng ta sẽ add thêm vài dòng để hỗ trợ bootstrap và AngularJS trong applicaion.js

//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require angular
//= require angular-resource
//= require bootstrap-sprockets
//= require_tree .

tạo một file bootstrap_config.scss để tạo css cho bootstrap.

@import "bootstrap-sprockets";
@import "bootstrap";

tiếp theo chúng ta cần tạo một ứng dụng angularJS. Để bắt đầu chúng ta cần tạo 1 file article.js và thực hiện như code bên dưới và mình sẽ giải thích về nó sau:

var articleCenter = angular.module('ArticleCenter', ['ngResource']);

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

articleCenter.controller("articlesController", function($scope,Article) {
  $scope.articles = Article.index()

  $scope.addArticle = function() {
    article = Article.save($scope.newArticle)

    $scope.articles.push(article)
    $scope.newArticle = {}
  }

  $scope.deleteArticle = function(index) {

    article = $scope.articles[index]
    article.delete(article)
    $scope.articles.splice(index, 1);
  }
})

Sau đây mình sẽ giải thích rõ hơn về đoạn code AngulaJS trên

var articleCenter = angular.module('ArticleCenter', ['ngResource']);

dòng code này định nghĩa một Angular module. AngularJS module có thể được coi như một component của ứng dụng. Bạn sẽ thấy chúng ta include 1 tham số ngResource, cái này cung cấp các phương thức truy xuất RESTful như rails.

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

tiếp theo đó là chúng ta định nghĩa 1 service, cái này sẽ tương tác giữa dữ liệu nhờ ngResource. trong AngularJS có 2 hình thức tạo service đó là .factory và .service tùy vào mục đích mà chúng ta sử dụng chúng, ở đây mình sẽ ko để cập đến nó, có thể mình sẽ nói về nó ở bài sau. 😃)

articleCenter.controller("articlesController", function($scope,Article) {
  $scope.articles = Article.index()

  $scope.addArticle = function() {
    article = Article.save($scope.newArticle)

    $scope.articles.push(article)
    $scope.newArticle = {}
  }

  $scope.deleteArticle = function(index) {

    article = $scope.articles[index]
    article.delete(article)
    $scope.articles.splice(index, 1);
  }
})

đoạn code trên là Controller trong AngularJS để tương tác với view của ứng dụng như Controller trong Rails vậy.

Giờ thì chúng ta sẽ tạo view cho ứng dụng :

<div class="container" ng-app="articleCenter">
  <h1>Article</h1>

  <div ng-controller="articlesController">
    <div class="well">
      <h3>Add a new article</h3>
      <form ng-submit="addArticle()">
        <div class="row">
          <div class="col-xs-6">
            <input type="text" ng-model="newArticle.title_name" class="form-control" placeholder="Title Name" />
          </div>
          <div class="col-xs-6">
            <input type="text" ng-model="newArticle.author" class="form-control" placeholder="Author" />
          </div>
        </div>
        <div class="row">
          <div class="col-xs-12">
            <br />
            <input type="text" ng-model="newArticle.content" class="form-control" placeholder="Content" />
          </div>
        </div>
        <div class="row">
          <div class="col-xs-12 text-center">
            <br />
            <input type="Submit" value="Add Article" class="btn btn-primary" />
          </div>
        </div>
      </form>
    </div>

    <h3>List Article</h3>
    <hr />
    <table class="table table-bordered table-striped">
      <thead>
        <tr>
          <th>Title Name</th>
          <th>Author</th>
          <th>Content</th>
          <th>&nbsp;</th>
        </tr>
      </thead>
      <tbody>
        <tr ng-show="!articles.length">
          <td colspan="4">No Article .</td>
        </tr>
        <tr ng-repeat="article in articles">
          <td>{{ article.title_name }}</td>
          <td>{{ article.author }}</td>
          <td>{{ article.content }}</td>
          <td><a class="btn btn-danger" ng-click="deleteArticle($index)">Remove</a></td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

Trong đoạn code trên directive ng-app là thuộc tính được dùng để chỉ định các phần được áp dụng AngularJS.

ng-controller ở đây nhăm xác định thành phần div đó được điều khiển và xử lý bởi Controller này.

ng-model ở đây hiều nôm na là nó sẽ lấy dữ liệu tương tác trực tiếp dữ view là controller một cách liên tục mà ko cần load lại trang, cơ chế này được gọi là Two way binding.

ng-repeat nó sẽ lặp qua từng phần tử của dữ liệu. còn đây :

<td>{{ article.title_name }}</td>
<td>{{ article.author }}</td>
<td>{{ article.content }}</td>

là cách Angular show dữ liệu ra view.

Như vậy chúng ta đã có được 1 ứng dụng kết hơp giữa Angular và Rails khá đơn giản, hi vọng mọi người sẽ thích, rất mong nhận được sự góp ý. Cảm ơn !