Integrate AngularJS to Rails 4.0
This post hasn't been updated for 3 years
1. Tổng quan
Dựa trên mô hình MVC, Ruby on Rails có khả năng tạo ra được một website hoàn chỉnh từ Database cho tới View. Tuy nhiên, với những ứng dụng có quy mô lớn hơn, việc server-side phải đảm nhiệm đủ cả 3 phần Model, View, Controller khiến cho hiệu năng hoạt động của server giảm đáng kể. Sự xuất hiện của các framework dành cho client-side như EmberJs, BackboneJS, AngularJs đã phần nào giúp đỡ server đảm nhiệm phần view để có thể tương tác với người sử dụng. Trong bài tìm hiểu trước đó, chúng ta đã đi tìm hiểu về EmberJs trên RoR. Hôm nay, bài viết sẽ đi tìm hiểu về một ứng dụng khác, AngularJS. angular_rr AngularJS cung cấp một mô hình thu nhỏ của MVC trên phía người sử dụng. Giống như RoR, phần view đảm nhiệm việc thực hiên các thẻ HTML cùng với CSS trong khi Controller và Model đóng vai trò tương tác với dữ liệu. Điều khác biệt lớn nhất là thay vì tương tác trực tiếp với database như RoR, AngularJS sử dụng dữ liệu JSON lấy được từ server-side để hiển thị lên View. Rails ở đây đóng vai trò thuần túy là tương tác trực tiếp với database (MySQL, MongoDB, Sqlite..) và cung cấp các API cho phép trả ra dữ liệu dựa trên các request từ Client side. Việc phân chia độc lập 2 phần Back-end và Front-end không những giúp tăng hiệu năng hoạt đông của server mà còn giúp developer phát triển Client side có nhiều chức năng hơn. Trong phần tiếp theo, chúng ta sẽ đi sâu vào việc tạo một ứng dụng trực tiếp sử dụng RoR và AngularJS
2. Xây dựng ứng dụng dựa trên RoR và AngularJS
Trong phần này, chúng ta sẽ đi xây dựng một ứng dụng tổng hợp tất cả các video trên Railscast. Có thể tham khảo ứng dụng trên github: https://github.com/ducthien1490/angular_railscast
2.1 Khởi tạo
Các khởi tạo cơ bản:
- Tạo application:
$rails new angular_railscast $cd angular_railscas
- Gemfile:
source "https://rubygems.org"
gem "rails", "4.1.2"
gem "sqlite3"
gem "sass-rails", "~> 4.0.3"
gem "uglifier", ">= 1.3.0"
gem "coffee-rails", "~> 4.0.0"
gem "turbolinks"
gem "jbuilder", "~> 2.0"
gem "sdoc", "~> 0.4.0", group: :doc
gem "spring", group: :development
gem "feedjira"
gem "pry-rails"
- Tạo migration, controller và model:
$rails g resource screencast title summary:text duration link published_at:datetime source video_url
$rake db:migrate
app/models/screencast.rb:
class Screencast < ActiveRecord::Base
validates_presence_of :title, :summary,
:duration, :link, :published_at, :source, :video_url
validates_uniqueness_of :video_url
end
- Import data từ Railscast sử dụng Feedjia:
lib/screencast_importer.rb:
require "feedjira"
class ScreencastImporter
def self.import_railscasts
Feedjira::Feed.add_common_feed_entry_element(:enclosure,
:value => :url, :as => :video_url)
Feedjira::Feed.add_common_feed_entry_element(
'itunes:duration', :as => :duration)
feed = Feedjira::Feed.fetch_and_parse(
"http://feeds.feedburner.com/railscasts")
feed.entries.each do |entry|
title = entry.title.gsub(/^#\d+\s/, '')
Screencast.where(video_url: entry.video_url).
first_or_create(
title: title,
summary: entry.summary,
duration: entry.duration,
link: entry.url,
published_at: entry.published,
source: "railscasts"
)
end
Screencast.where(source: 'railscasts').count
end
end
lib/tasks/sample_date.rake:
require "screencast_importer"
namespace :screencast_sync do
desc "Sync all Railscast videos"
task :railscasts => :environment do
total = ScreencastImporter.import_railscasts
puts "There are #{total} videos from Railscasts"
end
end
Chạy rake để tạo dữ liệu mẫu:
rake screencast_sync:railscasts
2.2 Xây dựng API ở server-side
Định nghĩa routes
config/routes.rb:
Rails.application.routes.draw do
scope :api do
get "/screencasts(.:format)" => "screencasts#index"
get "/screencasts/:id(.:format)" => "screencasts#show"
end
end
Controller
app/controllers/screencasts_controller.rb:
class ScreencastsController < ApplicationController
def index
render json: Screencast.all
end
def show
render json: Screencast.find(params[:id])
end
end
Controller được viết tuân thủ theo Restful action sẽ trả dữ liệu ra dưới dạng JSON.
2.3 Xây dựng client-side
Khởi tạo
- Như đã trình bày ở phần đầu, AngularJS tạo ra một mô hình MVC thu nhỏ trên client-side và tất cả đều được định nghĩa bên trong folder: app/assets/javascripts/angular/. Do đó ta cần phải tạo sub-folder dành cho AngularJS trong project:
$ mkdir -p app/assets/javascripts/angular/controllers \
app/assets/javascripts/angular/directives \
app/assets/javascripts/angular/services
- Bên cạnh đó, ta cần phải thêm vào thư viện của AngularJS. Về cơ bản, thư viên Angular có thể thêm bằng sử dụng gem angular hoặc sử dụng CDN. Ở trong phần này, chúng ta sẽ thêm AngularJS library vào trong project sử dụng CDN của Google:
<!DOCTYPE html>
<html>
<head>
<title>AngularjsApp</title>
<%= stylesheet_link_tag 'application', media: 'all'%>
<%= csrf_meta_tags %>
</head>
<body>
<header>
Angular Casts
</header>
<%= yield %>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular-resource.min.js"></script>
<%= javascript_include_tag 'application'%>
</body>
</html>
-
Tiếp đến, ta khởi tạo Angular App bằng một file js.coffee và thêm vào trong file application.js:
app/assets/javascripts/app.js.coffee:
window.App = angular.module("AngularCasts", ["ngResource"])
app/assets/javascripts/application.js:
//= require app
//= require_tree ./angular
Tạo home page
- Tạo ra một controller và định nghĩa root dành cho home page:
$ rails g controller home index
config/routes.rb:
Rails.application.routes.draw do
scope :api do
get "/screencasts(.:format)" => "screencasts#index"
get "/screencasts/:id(.:format)" => "screencasts#show"
end
root to: "home#index"
end
Từ đây trở đi là Angular !
-
Thêm vào view:
app/views/layouts/application.html.erb:
<!DOCTYPE html>
<html ng-app="AngularCasts">
......
<body>
<header>
Angular Casts
</header>
.....
- Khởi tạo Controller dành cho AngularJS: app/assets/javascripts/angular/controllers/screencasts_ctrl.js.coffee:
App.controller "ScreencastsCtrl", ["$scope", "Screencast", ($scope, Screencast) ->
$scope.message = "Angular Hello World!
]
-
Hiển thị message trên home page:
app/views/home/index.html.erb:
<div ng-controller="ScreencastsCtrl">
<h1>Message: {{message}}</h1>
</div>
=> truy cập thử http://localhost:3000 để kiểm tra message !
- Thêm một chút CSS (cũng không ít lắm ):
body {
font-size: 12px;
font-family: Helvetica, sans-serif;
background-color: #ddd;
margin: 0px;
}
header {
background-color: #4F4F4F;
color: #fff;
position: absolute;
height: 36px;
top: 0;
left: 0;
right: 0;
font-size: 18px;
line-height: 36px;
font-weight: bold;
padding-left: 15px;
}
#screencast-ctrl {
background-color: #fff;
position: absolute;
top: 37px;
width: 100%;
bottom: 0;
overflow: auto;
}
#screencast-list-container {
background-color: #fff;
position: absolute;
min-height: 700px;
width: 300px;
top: 37px;
left: 0;
bottom: 0;
overflow: auto;
-webkit-overflow-scrolling: touch;
ul {
margin: 0px;
list-style: none;
padding: 0px;
li {
cursor: pointer;
border-bottom: 1px solid #ddd;
padding: 0 10px;
}
}
h3 {
font-size: 14px;
small {
font-size: 12px;
color: #ccc;
font-weight: normal;
}
&.active {
color: red;
}
}
}
#screencast-view-container {
position: absolute;
border-left: 1px solid #d0d0d0;
top: 37px;
left: 300px;
right: 0;
bottom: 0;
background-color: #fff;
min-height: 700px;
padding: 5px 25px;
#player {
border: 1px solid #000;
max-width: 800px;
}
}
h1 {
padding-top: 30px;
}
-
Khởi tạo service và thêm service vào trong controller:
app/assets/javascripts/angular/services/screencast.js.coffee
App.factory "Screencast", ["$resource", ($resource) ->
$resource "/api/screencasts/:id", id: "@id"
]
app/assets/javascripts/controllers/screencasts_ctrl.js.coffee:
App.controller "ScreencastsCtrl", ["$scope", "Screencast", ($scope, Screencast) ->
$scope.screencasts = Screencast.query()
]
-
Hiển thị tất cả các record lên view:
app/views/home/index.html.erb:
<div ng-controller="ScreencastsCtrl">
<div id="screencast-list-container">
<ul>
<li ng-repeat="screencast in screencasts">
<h3>
{{screencast.title}}
<small>
({{screencast.duration}})
</small>
</h3>
</li>
</ul>
</div>
<div id="screencast-view-container">
<h2>{{selectedScreencast.title}}</h2>
<p>{{selectedScreencast.summary}}</p>
<div flow-player="" id="player"></div>
<p>
Published at {{selectedScreencast.published_at | date: "mediumDate"}}
- <a ng-href="{{selectedScreencast.link}}">{{selectedScreencast.link}}</a>
</p>
</div>
</div>
- Thêm vào trong Controller action show khi chọn một video nào đó:
App.controller "ScreencastsCtrl", ["$scope", "Screencast", ($scope, Screencast) ->
$scope.screencasts = Screencast.query()
$scope.selectedScreencast = null
$scope.showScreencast = (screencast) ->
$scope.selectedScreencast = screencast
]
-
Hiển thị video sử dụng Flowplayer và JQuery:
app/views/layouts/application.html.erb:
......
<%= yield %>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//releases.flowplayer.org/5.5.0/flowplayer.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular-resource.min.js"></script>
<%= javascript_include_tag 'application'%>
.....
- Thêm Flowplayer vào trong directives folder:
App.directive "flowPlayer", ->
(scope, element, attrs) ->
scope.$watch "selectedScreencast", (screencast) ->
if screencast
element.flowplayer
playlist: [[mp4: screencast.video_url]]
ratio: 9/14
- Thêm vào trong phần hiển thị chi tiết của video để có thể chạy video:
.............
<div id="screencast-view-container">
<h2>{{selectedScreencast.title}}</h2>
<p>{{selectedScreencast.summary}}</p>
<div flow-player="" id="player"></div>
<p>
Published at {{selectedScreencast.published_at | date: "mediumDate"}}
- <a ng-href="{{selectedScreencast.link}}">{{selectedScreencast.link}}</a>
</p>
</div>
.............
=> Refresh browser and enjoy Railscast from your application!
3. Kết luận
Việc sử dụng các Javascript framework như AngularJS hay EmberJS để xây dựng View đang là một xu thế tất yếu của Web hiện đại: Lớn hơn, nhanh hơn, tương tác phong phú hơn. Do phía client-side đã được tối ưu dựa trên nền Javascript nên server không phải tốn quá nhiều tài nguyên cho việc thực hiện và quản lý view. Thêm vào đó, việc sử dụng JSON data một cách linh hoạt giúp chúng ta có thể dễ dàng tương tác với các thư viện Javascript khác thiên về UI/UX như DHTMLX.
All Rights Reserved