Pengantar
AngularJS adalah framework JavaScript luar biasa yang memberi Anda data binding dua arah yang mudah digunakan dan cepat, sistem perintah canggih yang memungkinkan Anda membuat komponen kustom yang dapat digunakan kembali, dan masih banyak lagi. Socket.IO adalah wrapper dan polyfill lintas browser untuk websocket yang mempermudah pengembangan aplikasi real-time. Secara kebetulan, keduanya bekerja sama dengan cukup baik!
Saya telah menulis sebelumnya 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 memandu cara menulis aplikasi pesan instan. Aplikasi ini dibangun berdasarkan tutorial saya sebelumnya (menggunakan tumpukan node.js serupa di server), jadi sebaiknya Anda mempelajarinya terlebih dahulu jika tidak memahami Node.js atau Express.
Seperti biasa, Anda bisa mendapatkan produk jadinya di GitHub.
Prasyarat
Ada sedikit boilerplate untuk menyiapkan dan berintegrasi Socket.IO dengan Express, jadi saya membuat Angular Socket.IO Seed.
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 zip.
Setelah mendapatkan seed, Anda perlu mengambil beberapa dependensi dengan npm. Buka terminal ke direktori yang berisi seed tersebut, lalu jalankan:
npm install
Dengan menginstal dependensi ini, Anda dapat menjalankan aplikasi kerangka:
node app.js
dan melihatnya di browser Anda pada http://localhost:3000
untuk memastikan bahwa seed berfungsi seperti yang diharapkan.
Menentukan Fitur Aplikasi
Ada lebih dari beberapa cara untuk menulis aplikasi {i>chat<i}, jadi mari kita jelaskan fitur minimal yang akan kita miliki. Hanya akan ada satu ruang chat yang mencakup semua pengguna. Pengguna dapat memilih dan mengubah namanya, tetapi nama tersebut harus unik. Server akan menerapkan keunikan ini dan mengumumkan saat pengguna mengubah nama mereka. Klien harus mengekspos daftar pesan, dan daftar pengguna yang saat ini ada di ruang chat.
Front End yang 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 memberikan kolom dan tambahan:
/* 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
pada window
, sebaiknya enkapsulasinya dalam sistem Injeksi Dependensi AngularJS. Jadi, kita akan mulai dengan menulis layanan untuk menggabungkan objek socket
yang ditampilkan oleh Socket.IO. Ini luar biasa karena akan mempermudah pengujian pengontrol kita nanti. Buka public/js/services.js
dan ganti konten 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 di $scope.$apply
. Kode ini memberi tahu AngularJS bahwa kode perlu memeriksa status aplikasi dan memperbarui template jika ada perubahan setelah menjalankan callback yang diteruskan ke aplikasi. Secara internal, $http
berfungsi dengan cara yang sama; setelah beberapa XHR ditampilkan, kode ini akan memanggil $scope.$apply
, sehingga AngularJS dapat memperbarui tampilannya.
Perlu diperhatikan bahwa layanan ini tidak menggabungkan seluruh Socket.IO API (yang tersisa sebagai latihan untuk pembaca ;P ). Namun, layanan ini mencakup metode yang digunakan dalam tutorial ini, dan akan mengarahkan Anda ke arah yang benar jika ingin memperluasnya. Saya mungkin meninjau kembali menulis wrapper lengkap, tetapi itu 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 tambahkan logika untuk mengirim dan menerima pesan. Buka js/public/controllers.js
dan ganti konten 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 perutean 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 mendefinisikan objek untuk mempertahankan status server, sehingga nama pengguna 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 mendefinisikan sekumpulan nama, namun dengan API yang lebih masuk akal untuk domain server chat. Mari kita hubungkan ini 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 begitu, pengajuan permohonan Anda sudah selesai. Cobalah dengan menjalankan node app.js
. Aplikasi akan diperbarui secara real-time, berkat Socket.IO.
Kesimpulan
Ada banyak hal 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 riwayat pesan terkini untuk kepentingan pengguna baru yang bergabung dengan aplikasi.
Menulis aplikasi AngularJS yang memanfaatkan library lain menjadi mudah setelah Anda memahami cara menggabungkannya ke dalam layanan dan memberi tahu Angular bahwa model telah berubah. Selanjutnya saya berencana untuk membahas penggunaan AngularJS dengan D3.js, library visualisasi yang populer.
Referensi
Angular Socket.IO Seed Aplikasi Pesan Instan Selesai AngularJS Express Socket.IO`