Giriş
AngularJS, hem kullanımı kolay hem de hızlı iki yönlü veri bağlama, yeniden kullanılabilir özel bileşenler oluşturmanıza olanak tanıyan güçlü bir yönerge sistemi ve daha pek çok özellik sunan harika bir JavaScript çerçevesidir. Socket.IO, gerçek zamanlı uygulamaların geliştirilmesini kolaylaştıran tarayıcılar arası bir sarmalayıcı ve web soketleri için polyfill'dir. Bu iki araç birlikte oldukça iyi çalışır.
Daha önce Express ile AngularJS uygulaması yazma hakkında yazı yazdım. Bu sefer ise AngularJS uygulamasına gerçek zamanlı özellikler eklemek için Socket.IO'yu nasıl entegre edeceğiz hakkında yazacağım. Bu eğitimde, anlık mesajlaşma uygulaması yazma konusunda size yol göstereceğim. Bu eğitim, sunucu üzerinde benzer bir node.js paketi kullanan önceki eğitimimden yararlanır. Bu nedenle, Node.js veya Express'e aşina değilseniz önce bu eğitimi incelemenizi öneririm.
Her zaman olduğu gibi, bitmiş ürünü GitHub'dan indirebilirsiniz.
Ön koşullar
Socket.IO'yu ayarlamak ve Express ile entegre etmek için biraz şablon kod kullanmanız gerekir. Bu nedenle Angular Socket.IO Seed'i oluşturdum.
Başlamak için GitHub'dan angular-node-seed deposunu kopyalayabilirsiniz:
git clone git://github.com/btford/angular-socket-io-seed my-project
veya zip dosyası olarak indirebilirsiniz.
Tohumu aldıktan sonra npm ile birkaç bağımlı öğe almanız gerekir. Tohumun bulunduğu dizinde bir terminal açın ve aşağıdaki komutu çalıştırın:
npm install
Bu bağımlılıklar yüklendikten sonra iskelet uygulamayı çalıştırabilirsiniz:
node app.js
ve tohumun beklendiği gibi çalıştığından emin olmak için http://localhost:3000
adresindeki tarayıcınızda görün.
Uygulama özelliklerine karar verme
Sohbet uygulaması yazmanın birkaç farklı yolu vardır. Bu nedenle, uygulamamızda yer alacak temel özellikleri açıklayalım. Tüm kullanıcıların ait olacağı yalnızca bir sohbet odası olacaktır. Kullanıcılar adlarını seçebilir ve değiştirebilir ancak adlar benzersiz olmalıdır. Sunucu bu benzersizliği zorunlu kılar ve kullanıcılar adlarını değiştirdiğinde bunu duyurur. İstemci, mesajların ve şu anda sohbet odasında bulunan kullanıcıların listesini göstermelidir.
Basit bir kullanıcı arayüzü
Bu spesifikasyonla, Jade ile gerekli kullanıcı arayüzü öğelerini sağlayan basit bir ön uç oluşturabiliriz. views/index.jade
dosyasını açın ve block body
dosyasının içine şunu ekleyin:
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')
public/css/app.css
dosyasını açın ve sütunlar ile taşmalar sağlamak için CSS'yi ekleyin:
/* 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;
}
Socket.IO ile etkileşime geçme
Socket.IO, window
üzerinde bir io
değişkeni göstermesine rağmen bu değişkeni AngularJS'nin Bağımlılık Enjeksiyon sistemine yerleştirmek daha iyidir. Bu nedenle, Socket.IO tarafından döndürülen socket
nesnesini sarmalayacak bir hizmet yazarak başlayacağız. Bu, denetleyicimizi daha sonra test etmeyi çok daha kolaylaştıracağı için harika. public/js/services.js
dosyasını açıp içeriği şu şekilde değiştirin:
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);
}
});
})
}
};
});
Her soket geri çağırma işlevini $scope.$apply
içine sarmaladığımıza dikkat edin. Bu, AngularJS'ye, kendisine iletilen geri çağırma işlevi çalıştırıldıktan sonra uygulamanın durumunu kontrol etmesi ve bir değişiklik varsa şablonları güncellemesi gerektiğini söyler. Dahili olarak $http
aynı şekilde çalışır; bazı XHR döndükten sonra AngularJS'in görünümlerini buna göre güncelleyebilmesi için $scope.$apply
çağrılır.
Bu hizmetin Socket.IO API'nin tamamını kapsamadığını unutmayın (bu, okuyucuya bir alıştırma olarak bırakılmıştır ;P ). Ancak bu eğitimde kullanılan yöntemleri kapsar ve daha fazla bilgi edinmek isterseniz sizi doğru yöne yönlendirir. Tam bir sarmalayıcı yazma konusuna tekrar dönebilirim ancak bu eğitim kapsamında değil.
Artık denetleyicimizde, $http
ile yaptığımız gibi socket
nesnesini isteyebiliriz:
function AppCtrl($scope, socket) {
/* Controller logic */
}
Denetleyiciye mesaj gönderme ve alma mantığını ekleyelim. js/public/controllers.js
dosyasını açıp içeriğini aşağıdakiyle değiştirin:
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 = '';
};
}
Bu uygulamada yalnızca bir görünüm olacağından public/js/app.js
adresindeki yönlendirmeyi kaldırabilir ve aşağıdaki şekilde basitleştirebiliriz:
// Declare app level module which depends on filters, and services
var app = angular.module('myApp', ['myApp.filters', 'myApp.directives']);
Sunucuyu Yazma
routes/socket.js
adlı kişiyi aç. Kullanıcı adlarının benzersiz olması için sunucunun durumunu korumak üzere bir nesne tanımlamamız gerekir.
// 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
};
}());
Bu temel olarak bir dizi ad tanımlar ancak bir sohbet sunucusunun alanı için daha anlamlı olan API'ler kullanır. İstemcimizin yaptığı çağrılara yanıt vermek için bunu sunucunun soketine bağlayalım:
// 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);
});
};
Bu işlemle birlikte başvuru tamamlanmış olur. node app.js
komutunu çalıştırarak deneyin. Socket.IO sayesinde uygulama gerçek zamanlı olarak güncellenir.
Sonuç
Bu anlık mesajlaşma uygulamasına daha fazla şey ekleyebilirsiniz. Örneğin, boş mesajlar gönderebilirsiniz. Bunu istemci tarafında önlemek için ng-valid
ve sunucu tarafında bir kontrol kullanabilirsiniz. Sunucu, uygulamaya katılan yeni kullanıcılar için mesajların son geçmişini saklayabilir.
Diğer kitaplıkları nasıl bir hizmete sarmalayacağınızı ve Angular'ı bir modelin değiştiği konusunda nasıl bilgilendireceğinizi öğrendikten sonra, diğer kitaplıkları kullanan AngularJS uygulamaları yazmak kolaydır. Bir sonraki yazımda, popüler görselleştirme kitaplığı D3.js ile AngularJS'i kullanmayı ele alacağım.
Referanslar
Angular Socket.IO Tohumu Bitmiş Anlık Mesajlaşma Uygulaması AngularJS Express Socket.IO`