Hướng dẫn realtime notifications giữa Angular 2 và Rails api sử dụng Action cable(Phần 1).
Angular 2 & Rails Làm thế nào để realtime notifications giữa front-end-server và back-end-api-server? Sau một thời gian làm task về tạo realtime notification trong một dự án Rails, mình đặt ra câu hỏi là, Rails hỗ trợ rất nhiều trong ứng dụng thuần của Rails. Vậy khi chúng ta phải làm dự án ...
Angular 2 & Rails Làm thế nào để realtime notifications giữa front-end-server và back-end-api-server?
Sau một thời gian làm task về tạo realtime notification trong một dự án Rails, mình đặt ra câu hỏi là, Rails hỗ trợ rất nhiều trong ứng dụng thuần của Rails. Vậy khi chúng ta phải làm dự án với 2 phần, một phần là api và một phần là front-end thì Action cable sẽ hỗ trợ chúng ta thế nào?
Rails api thì về bản chất vẫn là Rails mà thôi, các hoạt động khác nó vẫn hoạt động tương tự như Rails thường, Action cable cũng vậy, vậy bạn nào đã từng làm việc với Action cable rồi thì có thể lướt qua bài viết, đến cuối bài mình mới tập trung vào vấn đề giao tiếp giữa hai server.
Để bắt đầu tìm hiểu về chủ đề này, trước tiên cần chuẩn bị một số kiến thức về Rails-api, Action cable trong rails cũng như một ít kiến thức về Angular 2.
Mình nói lướt qua một chút về những gì mình sẽ trình bày trong bài viết này nha:
- Tạo một Rails-api app để có thể xử lý thông báo,
- Tạo một kênh thông qua Action cable để stream-notifications realtime.
- Tạo một Angular 2 app để bắt notification từ Rails api.
- Cấu hình để 2 server có thể mapping với nhau.
- Stream notification giữa Angular vs Rails Api
Thôi giới thiệu thế đủ rồi, bắt tay vào thực hiện nào.
1. Tạo Rails server api.
Chạy lệnh sau để build một app thuần api, dùng hệ quản trị csdl là mysql luôn:
rails new api-app --api -d mysql
Để có notification để stream thì chúng ta cũng cần tạo các model cần thiết. Ở đây mình tạo các model sau:
- User
- Notification
rails g model User rails g model Notification
tại model user khai báo acsociation với model notification:
has_many :notifications
model notification:
belongs_to :user
File migration create_notification:
class CreateNotifications < ActiveRecord::Migration[5.1] def change create_table :notifications do |t| t.string :content t.boolean :read t.references :user t.timestamps end end end
File migration create users:
class CreateUsers < ActiveRecord::Migration[5.1] def change create_table :users do |t| t.string :name t.string :email t.text :password_digest t.timestamps end end end
Sau đó chạy migrate
rails db:create rails db:migrate
2. Tạo channel để stream notification
Tại model notification chúng ta tạo một call_back để khi có notification thì nó có thể stream về client.
class Notification < ApplicationRecord after_create :send_notification def send_notification # Xử lý gửi thông báo sau khi có thông báo mới end end
controller notifications:
Tạo controller đơn giản để xử lý thông báo, controller này mình chỉ cần hàm index và show thôi:
rails g controller api/v1/notifications
file controllers/api/v1/notification_controllers.rb
module Api module V1 class NotificationsController < ApplicationController before_action :load_notification, only: :show def index @notifications = Notification.all render json: {message: "", data: {notifications: @notifications}}, status: 200 end def show; end private def load_notification @notification = Notification.find_by id: params[:id] end end end end
Chúng ta cũng cần tạo luôn một job đơn giản để gửi notification và trong model gọi job đó khi notification được tạo xong.
rails g job notification_broadcast
class NotificationBroadcastJob < ApplicationJob queue_as :default def perform # Action cable sẽ bắn notification qua dòng code này end end
Sơ sơ đã vậy, bây giờ tiếp tục chúng ta phải tạo một Angular app:
3. Tạo angular app
Ở đây mình sẽ dùng angular 2.
ng new notification-app
Giao diện thì chúng ta sẽ tạo một component là header để chứa cái thanh thông báo
ng generate component header
<h1>Header works</h1>
ng generate component footer
<h1>Footer works</h1>
file app.component.html
<app-header></app-header> <app-footer></app-footer>
Vậy là chúng ta đã tạo được một front-end app để có thể bắt đầu bắt ghép nó với server api mà chúng ta đã tạo trước đó:
4. Cấu hình server để 2 server có thể bắt ghép với nhau.
Vì Rails có cơ chế bảo vệ bằng token, trong một app Rails thuần, authenticate_token được render dưới view mặc định trong header, trong form hay bất kỳ cái gì thuộc về rails mà cần có cơ chế xác thực, vì thế khi chúng ta tạo server khác mà muốn gọi đến rails để lấy api thì mặc định là không thể get được api đó.
Vì thế cần cấu hình trong config của Rails để các server khác có thể get api từ Rails.
Trong Rails
Trong gemfile: thêm gem rack-cors
gem "rack-cors"
bundle
trong fileapplication.rb chúng ta thêm đoạn code này: Mục đích nhằm cho phép localhost:4200 có thể gọi được api lên server api của chúng ta.
Rack-Cors cung cấp hỗ trợ cho Cross-Origin Resource Sharing (CORS) cho các ứng dụng web tương thích Rack.
Sử dụng nó để chúng ta có thể tạo request ajax chéo lên server Rails từ một server khác.
config.middleware.insert_before 0, Rack::Cors do allow do origins "http://localhost:4200" resource "*", headers: :any, expose: ["Authorization"], methods: %i[get post options put patch delete] end end
Chúng ta cần tạo một kênh để có thể stream notification chứ:
rails g channel notification
Running via Spring preloader in process 4112
create app/channels/notification_channel.rb
Config router như sau:
config/routes.rb
Rails.application.routes.draw do namespace :api do namespace :v1 do resources :notifications resources :users end end mount ActionCable.server => '/cable' end
Tiếp theo chúng ta tạo một notification broadcast job để gọi action cable stream notification:
rails generate job notification_broadcast
Running via Spring preloader in process 9571
invoke test_unit
create test/jobs/notification_broadcast_job_test.rb
create app/jobs/notification_broadcast_job.rb
File app/jobs/notification_broadcast_job.rb:
class NotificationBroadcastJob < ApplicationJob queue_as :default # override perform def perform count, notification # Action cable sẽ broadcast notification số lượng thông báo và nội dung thông báo ActionCable.server.broadcast "notification_channel", counter: count, notification: render_notification(notification) end # Hàm này để render notification dưới dạng json def render_notification notification ApplicationController.renderer.render json: {message: "", data: {notification: notification}} end end
Trong file models/notification.rb hàm send_notification như sau:
def send_notification # Truyền số lượng thông báo, và nội dung thông báo cho job NotificationBroadcastJob.perform_now(Notification.all.size, self) end
Sau các bước trên, hãy chạy server Rails lên, xem log của server, nếu thấy như sau tức là server đã thiết lập kênh thành công và bên angular có thể bắt được thông báo khi có thông báo mới.
Finished "/cable/" [WebSocket] for 127.0.0.1 at 2018-04-21 20:27:16 +0700 NotificationChannel stopped streaming from notification_channel Started GET "/cable" for 127.0.0.1 at 2018-04-21 20:27:17 +0700 Started GET "/cable/" [WebSocket] for 127.0.0.1 at 2018-04-21 20:27:17 +0700 Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
Như vậy là đã xong bên server, tiếp đến là bên angular nhé Cấu hình cho phép server localhost:4200 có thể giao tiếp với Rails qua các method đã được khai báo.
Angular app:
Vì rails nó có hỗ trợ cả method trong javascript luôn rồi nên khi sử dụng action cable trong ứng dụng thuần rails khá đơn giản, vậy với Angular thì sao, vì Angular nó chạy độc lập không liên quan gì với rails hết, vậy làm sao để nó bắt được notification từ Action cable mà đã stream từ Rails?
Trước đây khi băn khoăn về cái này, mình đã nghĩ chắc là Angular nó phải tạo request liên tục lên server để bắt response từ Action cable mất.
Câu trả lời cho giải pháp đó là nó khá tệ, sẽ khiến hệ thống chậm đi rất nhiều. Angular có một thư viện code cung cấp cho angular js. Tiếp đó thì angular 2, 4 còn hỗ trợ nhiều hơn nữa. Vậy là chúng ta không cần phải nghĩ nhiều về việc tạo request liên tục lên server nữa rồi.