Sử dụng Action cable với angularjs
Action Cable là một bước tiến mới của Rails, giúp chúng ta xây dựng các ứng dụng Real-time mà trước đây vốn chỉ có thể sử dụng thông qua Gem. Nó cho phép chúng ta có thể xây dựng các ứng dụng thời gian thực như: chat,... Trước đây, khi xây dựng một ứng real-time giữa s rails- angularjs, mình ...
Action Cable là một bước tiến mới của Rails, giúp chúng ta xây dựng các ứng dụng Real-time mà trước đây vốn chỉ có thể sử dụng thông qua Gem. Nó cho phép chúng ta có thể xây dựng các ứng dụng thời gian thực như: chat,...
Trước đây, khi xây dựng một ứng real-time giữa s rails- angularjs, mình thường dùng ajax hoặc các thư viện như PubNub,... Tuy nhiên thì mình vẫn không hài lòng lắm với cách này, vì viết ajax thì thường phải viết, xử lý nhiều, còn PubNub là bên thứ 3 và phải đăng ký tài khoản, nếu như bạn có nhiều tiền thì mới có nhiều tiện ích,... Gần đây, vì bị bắt tìm hiểu về action cable thì cá nhân mình thấy đây là một giải pháp rất hay. Thứ nhất, mình không phải dùng thư viện của bên thứ 3, có thể tự custom theo ý mình, và free. Tiếp đến, sử dụng action cable không phải xử lý quá nhiều, gọi lên server nhiều lần. Trong bài viết này mình sẽ viết ví dụ xây dựng một ứng dụng chat đơn giản, backen rails và frontend là angularjs.
Khai báo gem
Để sử dụng actioncable trong rails, bạn phải khai báo gem redis và puma
gem "redis", "~> 3.0" gem "puma"
Sau đó bundle install
Khai báo config
Ở nhiều bài hướng dẫn, mọi người thưởng hướng dẫn config ở router, js, html. Tuy nhiên, ở bài viết này, rails chỉ đóng vai trò làm serve vì vậy chúng ta chỉ cần khai báo ở filr routes.rb
mount ActionCable.server => '/cable
Tạo kênh chat trên server
Sau khi bundle install chúng ta sẽ có thư mục channels có cấu trúc như sau
├── app ├── channels ├── application_cable ├── channel.rb └── connection.rb
Module ApplicationCable có class Channel và class Connection đã được định nghĩa Class Connection sẽ dùng để xác thực các kết nối. Ví dụ, khi thiết lập kênh kết nối tới một kênh chat, ứng dụng sẽ yêu cầu xác thực người dùng.
module ApplicationCable class Connection < ActionCable::Connection::Base end end
Class Channel dùng để định nghĩa logic được chia sẻ giữa các kênh
module ApplicationCable class Channel < ActionCable::Channel::Base end end
Thực ra thì 2 file trên mình không dùng làm gì cả, chỉ giới thiệu cho mọi người thôi, với cả cho bài viết dài ra thôi. Chúng ta đã sử dụng Action Cable để tạo thành công một connection, đón nhận bất kì yêu cầu WebSocket nào tại địa chỉ ws://localhost:3000/cable1. Nhưng như vậy chưa đủ để tạo chức năng gửi tin nhắn real-time. Chúng ta cần định nghĩa cụ thể kênh gửi nhận tin nhắn và hướng dẫn các thành phần khác của ứng dụng truyền - nhận dữ liệu từ kênh này. Định nghĩa một kênh truyền với Action Cable rất dễ dàng. Chúng ta sẽ tạo ra một fileapp/channels/chat_channel` như sau:
class ChatChannel < ApplicationCable::Channel def subscribed stream_from "messages" end def receive(data) ActionCable.server.broadcast "messages", data.fetch('message') end end
Trong file trên, hàm subcribed khai báo các kênh sẽ được sử dụng để lắng nghe các kết nối đến channel Chat, tức là channle Chat sẽ lắng nghe các message từ kênh messages. Hàm receive định nghĩa các hành động khi có thông điệp được gửi về channel Chat, ở đây mỗi khi channel Chat nhận được một thông điệp thì sẽ tiến hành broadcast thông điệp này đến kênh messages, điều này giúp cho tất cả các user đang trong ở trong một channel có thể nhận được.
Xây dựng kênh chat trên client
Về phía client mình chọn sử dụng Angularjs, do vậy mình sử dụng thư viện angular-actioncable. Để add angular-actioncable vào project của mình bạn có thể dụng một trong các cách sau:
- Bower: bower install angular-actioncable --save
- Npm: npm install angular-actioncable --save
- CDN cho môi trường development https://rawgit.com/angular-actioncable/angular-actioncable/1.1.1/dist/angular-actioncable.js
- CDN cho production https://cdn.rawgit.com/angular-actioncable/angular-actioncable/1.1.1/dist/angular-actioncable.js Sau đó, ta sẽ add module angular-actioncable như sau:
angular.module('app', [ 'ngActionCable' ]) .run(function (ActionCableConfig){ ActionCableConfig.wsUri= "wss://localhost:3000/cable"; });
Ở đây, vì backend và frontend của mình chay ở 2 server riêng biết nên cần config thêm ActionCableConfig.wsUri= "wss://localhost:3000/cable" để actioncable biết địa chỉ websocket Tiếp đến, chúng ta sẽ viết một controller để kết nối, gửi nhận dữ liệu như sau:
angular.module('app') .controller('ChatController', function ($scope, ActionCableChannel){ $scope.inputText = ""; $scope.myData = []; // connect to ActionCable var consumer = new ActionCableChannel("ChatChannel"); var callback = function(message) { $scope.myData.push(message); }; consumer.subscribe(callback).then(function(){ $scope.sendToMyChannel = function(message){ consumer.send(message); }; $scope.$on("$destroy", function(){ consumer.unsubscribe().then(function(){ $scope.sendToMyChannel = undefined; }); }); }); });
Trong đó, ta tạo biến consumer là một action cable mới: var consumer = new ActionCableChannel("ChatChannel");. consumer có 2 function chính là subscribe() và unsubscribe(), giống như tên gọi chính là để subscribe là unsubscribe đến channel Chat, functionsend() dùng để gửi dữ liệu lên server Đến đây thì ứng dụng đã gần như hoàn thiện, sau đây là file html đơn giản dùng cho việc gửi và hiển thị dữ liệu
<body ng-app="app"> <section ng-controller="ChatController"> <ul> <li ng-repeat="datum in myData"> {{ datum }} </li> </ul> <input ng-model="inputText" /><button ng-click="sendToMyChannel(inputText)">Send</button> </section> <br /> <section> You can "<kbd>Ctrl+c</kbd>" + "<kbd>rails s</kbd>" and "<kbd>Ctrl+z</kbd>" + "<kbd>fg 1</kbd>" the server to see how it affects client states over time. Each client will maintain one and only one open connection and multiple clients will reconnect at slightly different times on server restart. </div> </div> </section> </body>
Trên đây là bài hướng dẫn tạo một ứng dụng chat đơn giản giữa rails-angularjs sử dụng Action cable, các bạn có thể xem một ví dụ source code ở đây https://github.com/Neil-Ni/rails5-actioncable-angular-demo
Angular-actioncable
Sau đây mình xin giới thiệu một số hàm, config của thư viện action-cable
Factory: ActionCableChannel
name | arguments | description |
---|---|---|
new | channelName:String channel, Params:Hash:optiona, l returns instance | Tạo mới và mở ActionCableChannel instance. function này bắt buộc phải có và phải viết đầu tiênvar consumer = new ActionCableChannel("ChatChannel", {chat_id: 1, user_id: 1}); |
subscribe | callback:Function, returns promise | subcribe đến một channel, callback thường là phục vụ cho việc nhận dữ liệu consumer.subscribe(function(message){ $scope.thing = message }); |
unsubscribe | callback:Function, returns promise | unscrible một channel, consumer.unsubscribe().then(function(){ $scope.sendToMyChannel = undefined; }) |
send | message: String, action: String:optional, returns promise | gửi một message lên serve consumer.send('message'); |
onConfirmSubscription | callback:Function | function được gọi mỗi khi server chấp nhận subscribe consumer.onConfirmSubscription(function(){ console.log('subscribed'); }); |
Factory: ActionCableSocketWrangler
Method
name | arguments | description |
---|---|---|
start | Khởi động services ngActionCable, việc này là default của service trừ khi bạn disable ActionCableSocketWrangler.start(); | |
stop | tắt services ngActionCable. ActionCableSocketWrangler.stop(); |
Properties
name | type | description |
---|---|---|
connected | Property:Boolean | ngActionCable đã start và đã connect. ActionCableSocketWrangler.connected; |
connecting | Property:Boolean | ngActionCable đã started and đang kết nối. ActionCableSocketWrangler.connecting; |
disconnected | Property:Boolean | ngActionCable đã dừng và ngưng kết nối. ActionCableSocketWrangler.disconnected; |
Configuration: ActionCableConfig
Properties
name | type | description |
---|---|---|
wsUri | String | khai báo URI để connect hay chính là địa chỉ websocket của server |
autoStart | Boolean | định nghĩa ngActionCable có tự động start hay không, default is true. Nếu như bạn config ActionCableConfig.autoStart= false; thì mỗi khi cần tạo một action cable mới bạn phải start service ngActionCable như đã nói ở trên |
debug | Boolean | hiển thị log thên console của trình duyệt, default là false. Nếu muốn set là true thì bạn cần thêm ActionCableConfig.debug= true; |