مقدمه
AngularJS یک چارچوب جاوا اسکریپت فوقالعاده است که به شما اتصال دو طرفه داده را میدهد که استفاده آسان و سریع است، یک سیستم دستورالعمل قدرتمند که به شما امکان میدهد از ایجاد اجزای سفارشی قابل استفاده مجدد و بسیاری موارد دیگر استفاده کنید. Socket.IO یک بسته بندی بین مرورگر و polyfill برای وب سوکت ها است که توسعه برنامه های بلادرنگ را آسان می کند. اتفاقاً این دو با هم خیلی خوب کار می کنند!
قبلاً در مورد نوشتن یک برنامه AngularJS با Express نوشتهام، اما این بار در مورد نحوه ادغام Socket.IO برای افزودن ویژگیهای بلادرنگ به یک برنامه AngularJS خواهم نوشت. در این آموزش، من قصد دارم در مورد نوشتن یک برنامه پیامرسانی فوری قدم بگذارم. این بر اساس آموزش قبلی من (استفاده از پشته node.js مشابه در سرور) است، بنابراین توصیه می کنم اگر با Node.js یا Express آشنایی ندارید، ابتدا آن را بررسی کنید.
مثل همیشه، می توانید محصول نهایی را در Github دریافت کنید .
پیش نیازها
راه اندازی و ادغام Socket.IO با Express کمی سخت است، بنابراین من Angular Socket.IO Seed را ایجاد کردم.
برای شروع، می توانید مخزن angular-node-seed را از Github شبیه سازی کنید:
git clone git://github.com/btford/angular-socket-io-seed my-project
یا به صورت فشرده دانلود کنید .
هنگامی که دانه را به دست آوردید، باید چند وابستگی را با npm بگیرید. یک ترمینال را به دایرکتوری با seed باز کنید و اجرا کنید:
npm install
با نصب این وابستگی ها، می توانید برنامه اسکلت را اجرا کنید:
node app.js
و آن را در مرورگر خود در http://localhost:3000
ببینید تا مطمئن شوید که seed همانطور که انتظار می رود کار می کند.
تصمیم گیری در مورد ویژگی های برنامه
بیش از چند روش مختلف برای نوشتن یک برنامه چت وجود دارد، بنابراین بیایید حداقل ویژگی هایی را که برنامه ما خواهد داشت را شرح دهیم. تنها یک اتاق گفتگو وجود خواهد داشت که همه کاربران به آن تعلق خواهند داشت. کاربران می توانند نام خود را انتخاب و تغییر دهند، اما نام ها باید منحصر به فرد باشند. سرور این منحصر به فرد بودن را اعمال می کند و زمانی که کاربران نام خود را تغییر می دهند، اعلام می کند. مشتری باید لیستی از پیام ها و لیستی از کاربرانی که در حال حاضر در اتاق چت هستند را در معرض دید قرار دهد.
یک جلوی ساده
با این مشخصات، میتوانیم یک نمای ظاهری ساده با Jade ایجاد کنیم که عناصر UI لازم را ارائه میکند. views/index.jade
را باز کنید و این را در داخل 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')
public/css/app.css
را باز کنید و CSS را برای ارائه ستونها و سرریزها اضافه کنید:
/* 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
اگرچه Socket.IO یک متغیر io
را در window
نمایش می دهد، بهتر است آن را در سیستم تزریق وابستگی AngularJS کپسوله کنید. بنابراین، ما با نوشتن یک سرویس برای بسته بندی شی socket
بازگشتی توسط Socket.IO شروع می کنیم. این عالی است، زیرا آزمایش کنترلر ما را بعداً بسیار آسان تر می کند. public/js/services.js
را باز کنید و محتویات زیر را جایگزین کنید:
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);
}
});
})
}
};
});
توجه داشته باشید که ما هر سوکت را در $scope.$apply
قرار می دهیم. این به AngularJS میگوید که باید وضعیت برنامه را بررسی کرده و در صورت ایجاد تغییر پس از اجرای callback ارسال شده به آن، الگوها را بهروزرسانی کند. در داخل، $http
به همین ترتیب کار می کند. پس از بازگرداندن مقداری XHR، $scope.$apply
فراخوانی میکند تا AngularJS بتواند نماهای خود را مطابق با آن بهروزرسانی کند.
توجه داشته باشید که این سرویس کل API Socket.IO را در بر نمی گیرد (که به عنوان تمرینی برای خواننده باقی می ماند ;P ). با این حال، روشهای مورد استفاده در این آموزش را پوشش میدهد و اگر میخواهید آن را گسترش دهید، باید شما را در مسیر درست راهنمایی کند. ممکن است دوباره به نوشتن یک wrapper کامل مراجعه کنم، اما این فراتر از محدوده این آموزش است.
اکنون، در کنترلر خود، میتوانیم شی socket
را بخواهیم، دقیقاً مانند $http
:
function AppCtrl($scope, socket) {
/* Controller logic */
}
در داخل کنترلر، بیایید منطق ارسال و دریافت پیام را اضافه کنیم. js/public/controllers.js
را باز کنید و مطالب زیر را جایگزین کنید:
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 = '';
};
}
این برنامه فقط یک نما دارد، بنابراین میتوانیم مسیریابی را از public/js/app.js
حذف کنیم و آن را به صورت زیر ساده کنیم:
// Declare app level module which depends on filters, and services
var app = angular.module('myApp', ['myApp.filters', 'myApp.directives']);
نوشتن سرور
routes/socket.js
باز کنید. ما باید یک شی برای حفظ وضعیت سرور تعریف کنیم تا نام کاربری منحصر به فرد باشد.
// 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
};
}());
این اساساً مجموعهای از نامها را تعریف میکند، اما با APIهایی که برای دامنه یک سرور چت منطقیتر هستند. بیایید این را به سوکت سرور متصل کنیم تا به تماس هایی که مشتری ما می گیرد پاسخ دهیم:
// 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);
});
};
و با آن، برنامه باید کامل شود. با اجرای node app.js
آن را امتحان کنید. به لطف Socket.IO، برنامه باید در زمان واقعی به روز شود.
نتیجه گیری
چیزهای بیشتری وجود دارد که می توانید به این برنامه پیام رسانی فوری اضافه کنید. به عنوان مثال، می توانید پیام های خالی ارسال کنید. می توانید از ng-valid
برای جلوگیری از این امر در سمت مشتری و بررسی روی سرور استفاده کنید. شاید سرور بتواند تاریخچه اخیر پیام ها را به نفع کاربران جدیدی که به برنامه ملحق می شوند نگه دارد.
نوشتن برنامههای AngularJS که از کتابخانههای دیگر استفاده میکنند، زمانی آسان است که بدانید چگونه آنها را در یک سرویس بپیچید و به Angular اطلاع دهید که مدلی تغییر کرده است. در ادامه قصد دارم با استفاده از AngularJS با D3.js ، کتابخانه محبوب تجسم، پوشش دهم.
مراجع
Angular Socket.IO Seed Finished برنامه پیامرسانی فوری AngularJS Express Socket.IO `