Pengantar
AngularJS adalah framework JavaScript yang luar biasa yang memberi Anda binding data dua arah yang mudah digunakan dan cepat, sistem perintah yang canggih yang memungkinkan Anda membuat komponen kustom yang dapat digunakan kembali, dan banyak lagi. Socket.IO adalah wrapper lintas browser dan polyfill untuk websocket yang memudahkan pengembangan aplikasi real-time. Kebetulan, keduanya bekerja sama dengan cukup baik.
Sebelumnya, saya telah menulis tentang menulis aplikasi AngularJS dengan Express, tetapi kali ini saya akan menulis tentang cara mengintegrasikan Socket.IO untuk menambahkan fitur real-time ke aplikasi AngularJS. Dalam tutorial ini, saya akan menjelaskan cara menulis aplikasi pesan instan. Tutorial ini dibuat berdasarkan tutorial saya sebelumnya (menggunakan stack node.js serupa di server), jadi sebaiknya lihat tutorial tersebut terlebih dahulu jika Anda tidak terbiasa dengan Node.js atau Express.
Seperti biasa, Anda dapat mendapatkan produk jadi di GitHub.
Prasyarat
Ada sedikit boilerplate untuk menyiapkan dan mengintegrasikan Socket.IO dengan Express, jadi saya membuat Seed Socket.IO Angular.
Untuk memulai, Anda dapat meng-clone repo angular-node-seed dari GitHub:
git clone git://github.com/btford/angular-socket-io-seed my-project
atau download sebagai file zip.
Setelah memiliki seed, Anda perlu mengambil beberapa dependensi dengan npm. Buka terminal ke direktori dengan seed, lalu jalankan:
npm install
Setelah menginstal dependensi ini, Anda dapat menjalankan aplikasi kerangka:
node app.js
dan lihat di browser Anda di http://localhost:3000
untuk memastikan bahwa seed berfungsi seperti yang diharapkan.
Memutuskan Fitur Aplikasi
Ada lebih dari beberapa cara untuk menulis aplikasi chat, jadi mari kita jelaskan fitur minimal yang akan dimiliki aplikasi kita. Hanya akan ada satu ruang chat yang menjadi milik semua pengguna. Pengguna dapat memilih dan mengubah namanya, tetapi nama tersebut harus unik. Server akan menerapkan keunikan ini dan mengumumkan saat pengguna mengubah namanya. Klien harus menampilkan daftar pesan, dan daftar pengguna yang saat ini berada di ruang chat.
Front End Sederhana
Dengan spesifikasi ini, kita dapat membuat frontend sederhana dengan Jade yang menyediakan elemen UI yang diperlukan. Buka views/index.jade
dan tambahkan ini di dalam block body
:
div(ng-controller='AppCtrl')
.col
h3 Messages
.overflowable
p(ng-repeat='message in messages') :
.col
h3 Users
.overflowable
p(ng-repeat='user in users')
.clr
form(ng-submit='sendMessage()')
| Message:
input(size='60', ng-model='message')
input(type='submit', value='Send')
.clr
h3 Change your name
p Your current user name is
form(ng-submit='changeName()')
input(ng-model='newName')
input(type='submit', value='Change Name')
Buka public/css/app.css
dan tambahkan CSS untuk menyediakan kolom dan overflow:
/* app css stylesheet */
.overflowable {
height: 240px;
overflow-y: auto;
border: 1px solid #000;
}
.overflowable p {
margin: 0;
}
/* poor man's grid system */
.col {
float: left;
width: 350px;
}
.clr {
clear: both;
}
Berinteraksi dengan Socket.IO
Meskipun Socket.IO mengekspos variabel io
di window
, sebaiknya enkapsulasi dalam sistem Injeksi Dependensi AngularJS. Jadi, kita akan mulai dengan menulis layanan untuk menggabungkan objek socket
yang ditampilkan oleh Socket.IO. Ini sangat bagus, karena akan mempermudah pengujian pengontrol kita nanti. Buka public/js/services.js
dan ganti kontennya dengan:
app.factory('socket', function ($rootScope) {
var socket = io.connect();
return {
on: function (eventName, callback) {
socket.on(eventName, function () {
var args = arguments;
$rootScope.$apply(function () {
callback.apply(socket, args);
});
});
},
emit: function (eventName, data, callback) {
socket.emit(eventName, data, function () {
var args = arguments;
$rootScope.$apply(function () {
if (callback) {
callback.apply(socket, args);
}
});
})
}
};
});
Perhatikan bahwa kita menggabungkan setiap callback soket dalam $scope.$apply
. Hal ini memberi tahu AngularJS bahwa AngularJS perlu memeriksa status aplikasi dan memperbarui template jika ada perubahan setelah menjalankan callback yang diteruskan ke AngularJS. Secara internal, $http
berfungsi dengan cara yang sama; setelah beberapa XHR ditampilkan, $http
akan memanggil $scope.$apply
, sehingga AngularJS dapat memperbarui tampilannya sebagaimana mestinya.
Perhatikan bahwa layanan ini tidak menggabungkan seluruh Socket.IO API (yang dibiarkan sebagai latihan bagi pembaca ;P ). Namun, layanan ini mencakup metode yang digunakan dalam tutorial ini, dan akan mengarahkan Anda ke arah yang benar jika Anda ingin memperluasnya. Saya mungkin akan meninjau kembali penulisan wrapper lengkap, tetapi itu berada di luar cakupan tutorial ini.
Sekarang, dalam pengontrol, kita dapat meminta objek socket
, seperti yang kita lakukan dengan $http
:
function AppCtrl($scope, socket) {
/* Controller logic */
}
Di dalam pengontrol, mari kita tambahkan logika untuk mengirim dan menerima pesan. Buka js/public/controllers.js
dan ganti kontennya dengan kode berikut:
function AppCtrl($scope, socket) {
// Socket listeners
// ================
socket.on('init', function (data) {
$scope.name = data.name;
$scope.users = data.users;
});
socket.on('send:message', function (message) {
$scope.messages.push(message);
});
socket.on('change:name', function (data) {
changeName(data.oldName, data.newName);
});
socket.on('user:join', function (data) {
$scope.messages.push({
user: 'chatroom',
text: 'User ' + data.name + ' has joined.'
});
$scope.users.push(data.name);
});
// add a message to the conversation when a user disconnects or leaves the room
socket.on('user:left', function (data) {
$scope.messages.push({
user: 'chatroom',
text: 'User ' + data.name + ' has left.'
});
var i, user;
for (i = 0; i < $scope.users.length; i++) {
user = $scope.users[i];
if (user === data.name) {
$scope.users.splice(i, 1);
break;
}
}
});
// Private helpers
// ===============
var changeName = function (oldName, newName) {
// rename user in list of users
var i;
for (i = 0; i < $scope.users.length; i++) {
if ($scope.users[i] === oldName) {
$scope.users[i] = newName;
}
}
$scope.messages.push({
user: 'chatroom',
text: 'User ' + oldName + ' is now known as ' + newName + '.'
});
}
// Methods published to the scope
// ==============================
$scope.changeName = function () {
socket.emit('change:name', {
name: $scope.newName
}, function (result) {
if (!result) {
alert('There was an error changing your name');
} else {
changeName($scope.name, $scope.newName);
$scope.name = $scope.newName;
$scope.newName = '';
}
});
};
$scope.sendMessage = function () {
socket.emit('send:message', {
message: $scope.message
});
// add the message to our model locally
$scope.messages.push({
user: $scope.name,
text: $scope.message
});
// clear message box
$scope.message = '';
};
}
Aplikasi ini hanya akan menampilkan satu tampilan, sehingga kita dapat menghapus pemilihan rute dari public/js/app.js
dan menyederhanakannya menjadi:
// Declare app level module which depends on filters, and services
var app = angular.module('myApp', ['myApp.filters', 'myApp.directives']);
Menulis Server
Buka routes/socket.js
. Kita perlu menentukan objek untuk mempertahankan status server, sehingga nama pengguna bersifat unik.
// Keep track of which names are used so that there are no duplicates
var userNames = (function () {
var names = {};
var claim = function (name) {
if (!name || userNames[name]) {
return false;
} else {
userNames[name] = true;
return true;
}
};
// find the lowest unused "guest" name and claim it
var getGuestName = function () {
var name,
nextUserId = 1;
do {
name = 'Guest ' + nextUserId;
nextUserId += 1;
} while (!claim(name));
return name;
};
// serialize claimed names as an array
var get = function () {
var res = [];
for (user in userNames) {
res.push(user);
}
return res;
};
var free = function (name) {
if (userNames[name]) {
delete userNames[name];
}
};
return {
claim: claim,
free: free,
get: get,
getGuestName: getGuestName
};
}());
Ini pada dasarnya menentukan sekumpulan nama, tetapi dengan API yang lebih sesuai untuk domain server chat. Mari kita hubungkan ke soket server untuk merespons panggilan yang dilakukan klien kita:
// export function for listening to the socket
module.exports = function (socket) {
var name = userNames.getGuestName();
// send the new user their name and a list of users
socket.emit('init', {
name: name,
users: userNames.get()
});
// notify other clients that a new user has joined
socket.broadcast.emit('user:join', {
name: name
});
// broadcast a user's message to other users
socket.on('send:message', function (data) {
socket.broadcast.emit('send:message', {
user: name,
text: data.message
});
});
// validate a user's name change, and broadcast it on success
socket.on('change:name', function (data, fn) {
if (userNames.claim(data.name)) {
var oldName = name;
userNames.free(oldName);
name = data.name;
socket.broadcast.emit('change:name', {
oldName: oldName,
newName: name
});
fn(true);
} else {
fn(false);
}
});
// clean up when a user leaves, and broadcast it to other users
socket.on('disconnect', function () {
socket.broadcast.emit('user:left', {
name: name
});
userNames.free(name);
});
};
Dengan demikian, permohonan akan selesai. Coba dengan menjalankan node app.js
. Aplikasi akan diperbarui secara real-time, berkat Socket.IO.
Kesimpulan
Ada banyak hal lain yang dapat Anda tambahkan ke aplikasi pesan instan ini. Misalnya, Anda dapat mengirim pesan kosong. Anda dapat menggunakan ng-valid
untuk mencegah hal ini di sisi klien, dan pemeriksaan di server. Mungkin server dapat menyimpan histori pesan terbaru untuk kepentingan pengguna baru yang bergabung ke aplikasi.
Menulis aplikasi AngularJS yang menggunakan library lain menjadi mudah setelah Anda memahami cara menggabungkannya dalam layanan dan memberi tahu Angular bahwa model telah berubah. Selanjutnya, saya berencana membahas penggunaan AngularJS dengan D3.js, library visualisasi yang populer.
Referensi
Angular Socket.IO Seed Finished Instant Messaging App AngularJS Express Socket.IO`