Giriş
AngularJS, size hem kullanımı kolay hem de hızlı olan iki yönlü veri bağlama olanağı, yeniden kullanılabilir özel bileşenler oluşturmanıza ve çok daha fazlasını kullanmanıza olanak tanıyan güçlü bir yönerge sistemi sunan mükemmel bir JavaScript çerçevesidir. Socket.IO, gerçek zamanlı uygulama geliştirmeyi kolaylaştıran websockets için tarayıcılar arası sarmalayıcı ve çoklu dolgudur. Ne yazık ki ikisi birlikte gayet iyi çalışıyor!
Daha önce Express ile AngularJS uygulaması yazma hakkında yazmıştım, ancak bu sefer AngularJS uygulamasına gerçek zamanlı özellikler eklemek için Socket.IO'yu nasıl entegre edeceğimi anlatacağım. Bu eğiticide anlık mesajlaşma uygulaması yazma işlemini adım adım anlatacağım. Bu çalışma, (sunucuda benzer bir node.js yığını kullanarak) önceki eğiticimi temel almaktadır. Bu nedenle Node.js veya Express'e aşina değilseniz önce bu web sitesini kontrol etmenizi öneririm.
Her zaman olduğu gibi bitmiş ürünü GitHub'dan alabilirsiniz.
Ön koşullar
Socket.IO'yu kurmak ve Express ile entegre etmek için biraz ortak metin var, bu yüzden Angular Socket.IO Seed'i oluşturdum.
Başlamak için GitHub'dan angle-node-seed deposunu klonlayabilirsiniz:
git clone git://github.com/btford/angular-socket-io-seed my-project
veya zip olarak indirin.
Başlangıç noktası edindikten sonra, npm ile birkaç bağımlılığı belirlemeniz gerekir. Çekirdek dizinin bulunduğu bir terminali açın ve şu komutu çalıştırın:
npm install
Bu bağımlılıklar yüklendikten sonra iskelet uygulamasını çalıştırabilirsiniz:
node app.js
ve başlangıç noktasının beklendiği gibi çalıştığından emin olmak için http://localhost:3000
adresindeki tarayıcıda kontrol edin.
Uygulama Özelliklerine Karar Verme
Sohbet uygulaması yazmanın birkaç farklı yolu vardır. Bu nedenle bizde bulunan minimum özellikleri açıklayalım. Tüm kullanıcıların ait olacağı tek bir sohbet odası olacak. Kullanıcılar adlarını seçip 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 bildirir. İstemci, mesajların listesini ve o anda sohbet odasında olan kullanıcıların listesini göstermelidir.
Basit Bir Kullanıcı Arabirimi
Bu spesifikasyon ile, Jade ile gerekli kullanıcı arayüzü öğelerini sağlayan basit bir kullanıcı arabirimi yapabiliriz. views/index.jade
öğesini açın ve block body
içine 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
öğesini açın ve sütunları ve 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şimde bulunma
Socket.IO, window
üzerinde bir io
değişkeni gösterse de bunu AngularJS'nin Bağımlılık Yerleştirme sistemine kapsüllemek daha iyidir. Şimdi, Socket.IO tarafından döndürülen socket
nesnesini sarmalayacak bir hizmet yazarak başlayacağız. Kumandamızı daha sonra test etmemizi kolaylaştıracağından bu muhteşem bir özellik. public/js/services.js
dosyasını açın ve içeriği şununla 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 yuva geri çağırmasını $scope.$apply
ile sarmaladığımıza dikkat edin. Bu, AngularJS'ye, uygulamanın durumunu kontrol etmesi ve kendisine iletilen geri çağırmayı çalıştırdıktan sonra bir değişiklik olduysa şablonları güncellemesi gerektiğini bildirir. $http
dahili olarak aynı şekilde çalışır; bir miktar XHR geri döndükten sonra, AngularJS'nin görünümlerini uygun şekilde güncelleyebilmesi için $scope.$apply
yöntemini çağırır.
Bu hizmetin tüm Socket.IO API'sını sarmadığını (okuyucu için bir alıştırma olarak ;P ) unutmayın. Ancak, bu eğiticide kullanılan yöntemleri ele alır ve genişletmek istiyorsanız sizi doğru yöne yönlendirmelidir. Sarmalayıcının tamamını tekrar yazabilirsiniz, ancak bunlar bu eğiticinin kapsamı dışındadır.
Artık denetleyicimizde $http
ile istediğimiz gibi socket
nesnesini isteyebiliriz:
function AppCtrl($scope, socket) {
/* Controller logic */
}
Kumandanın içerisine ileti gönderme ve alma mantığını ekleyelim. js/public/controllers.js
dosyasını açın ve içeriği şununla 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 uygulama yalnızca bir görünüm içerecek, dolayısıyla yönlendirmeyi public/js/app.js
öğesinden kaldırabilir ve şu şekilde basitleştirebiliriz:
// Declare app level module which depends on filters, and services
var app = angular.module('myApp', ['myApp.filters', 'myApp.directives']);
Sunucuya Yazma
routes/socket.js
adlı kişiyi aç. Kullanıcı adlarının benzersiz olması için sunucunun durumunu korumak amacıyla 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 grup adı tanımlar ancak sohbet sunucusunun alanı için daha anlamlı olan API'ları 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);
});
};
Böylece başvuru da tamamlanmış olur. node app.js
uygulamasını çalıştırarak deneyin. Uygulama, Socket.IO sayesinde gerçek zamanlı olarak güncellenir.
Sonuç
Bu anlık mesajlaşma uygulamasına ekleyebileceğiniz daha pek çok şey var. Örneğin, boş iletiler gönderebilirsiniz. İstemci tarafında bu durumun önüne geçmek ve sunucu üzerinde bir denetim gerçekleştirmek için ng-valid
kullanabilirsiniz. Belki de sunucu, uygulamaya katılan yeni kullanıcıların yararına iletilerin yakın zamandaki bir geçmişini saklayabilir.
Diğer kitaplıklardan yararlanan AngularJS uygulamaları yazmak için söz konusu uygulamaları bir hizmette nasıl sarmalayacağınızı anlayıp Angular'a bir modelin değiştiğini bildirmeniz yeterlidir. Bir sonraki derste, popüler görselleştirme kitaplığı olan D3.js ile AngularJS'yi kullanma konusunu ele almayı planlıyorum.
Referanslar
Angular Socket.IO Seed Tamamlanmış Anlık Mesajlaşma Uygulaması AngularJS Express Socket.IO"