Einleitung
AngularJS ist ein praktisches JavaScript-Framework, das eine bidirektionale Datenbindung bietet, die sowohl einfach als auch schnell ist, ein leistungsstarkes Anweisungssystem, mit dem Sie wiederverwendbare benutzerdefinierte Komponenten erstellen können, und vieles mehr. Socket.IO ist ein browserübergreifender Wrapper und Polyfill für WebSockets, der die Entwicklung von Echtzeitanwendungen zum Kinderspiel macht. Übrigens funktionieren die beiden ziemlich gut zusammen!
Ich habe bereits über das Schreiben einer AngularJS-Anwendung mit Express geschrieben. Dieses Mal geht es jedoch um die Integration von Socket.IO, um einer AngularJS-Anwendung Echtzeitfunktionen hinzuzufügen. In dieser Anleitung geht es um das Schreiben einer Instant Messaging-App. Sie baut auf meiner vorherigen Anleitung auf, bei der ein ähnlicher node.js-Stack auf dem Server verwendet wird. Wenn Sie also nicht mit Node.js oder Express vertraut sind, sollten Sie dies zuerst prüfen.
Du kannst das fertige Produkt wie immer auf GitHub herunterladen.
Voraussetzungen
Es gibt einige Textbausteine, um Socket.IO einzurichten und in Express einzubinden. Deshalb habe ich den Angular Socket.IO Seed erstellt.
Sie können entweder das Repository „Angular-node-seed“ aus GitHub klonen, um zu beginnen:
git clone git://github.com/btford/angular-socket-io-seed my-project
oder als ZIP-Datei herunterladen.
Sobald Sie den Seed haben, müssen Sie mit npm ein paar Abhängigkeiten abrufen. Öffnen Sie ein Terminal im Verzeichnis mit dem Startwert und führen Sie folgenden Befehl aus:
npm install
Wenn diese Abhängigkeiten installiert sind, können Sie die grundlegende Anwendung ausführen:
node app.js
und prüfen Sie sie in Ihrem Browser unter http://localhost:3000
, um sicherzustellen, dass sie wie erwartet funktioniert.
Entscheidung für App-Funktionen
Es gibt mehr als nur unterschiedliche Möglichkeiten, eine Chat-Anwendung zu schreiben. Im Folgenden werden die Mindestfunktionen beschrieben, die unsere Chat-Anwendung haben wird. Es wird nur einen Chatroom geben, dem alle Nutzer angehören sollen. Nutzer können ihren Namen auswählen und ändern, aber die Namen müssen eindeutig sein. Der Server erzwingt diese Eindeutigkeit und benachrichtigt Nutzer, wenn sie ihre Namen ändern. Der Client sollte eine Liste mit Nachrichten und eine Liste der Nutzer anzeigen, die sich derzeit im Chat-Raum befinden.
Ein einfaches Frontend
Mit dieser Spezifikation können wir mit Jade ein einfaches Frontend erstellen, das die erforderlichen UI-Elemente bereitstellt. Öffnen Sie views/index.jade
und fügen Sie Folgendes in block body
ein:
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')
Öffnen Sie public/css/app.css
und fügen Sie den CSS-Code hinzu, um Spalten und Überläufe bereitzustellen:
/* 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;
}
Mit Socket.IO interagieren
Obwohl Socket.IO eine io
-Variable im window
zur Verfügung stellt, ist es besser, sie in das Dependency Injection-System von AngularJS zu kapseln. Wir beginnen mit dem Schreiben eines Dienstes, um das von Socket.IO zurückgegebene socket
-Objekt zu verpacken. Das ist genial, denn so lässt sich der Controller später viel leichter testen. Öffnen Sie public/js/services.js
und ersetzen Sie den Inhalt durch:
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);
}
});
})
}
};
});
Beachten Sie, dass wir jeden Socket-Callback in $scope.$apply
verpacken. Dadurch wird AngularJS angewiesen, den Status der Anwendung zu überprüfen und die Vorlagen zu aktualisieren, wenn nach dem Ausführen des an sie übergebenen Callbacks eine Änderung aufgetreten ist. Intern funktioniert $http
auf die gleiche Weise. Nachdem einige XHR-Daten zurückgegeben wurden, wird $scope.$apply
aufgerufen, sodass AngularJS seine Ansichten entsprechend aktualisieren kann.
Beachten Sie, dass dieser Dienst nicht die gesamte Socket.IO API umschließt (die links als Übung für den Leser ;P ). Allerdings behandelt er die in dieser Anleitung verwendeten Methoden und sollte Sie in die richtige Richtung weisen, wenn Sie sie erweitern möchten. Ich schreibe vielleicht noch einmal einen vollständigen Wrapper, aber das würde den Rahmen dieser Anleitung sprengen.
Jetzt können wir in unserem Controller nach dem socket
-Objekt fragen, ähnlich wie bei $http
:
function AppCtrl($scope, socket) {
/* Controller logic */
}
Im Controller fügen wir Logik zum Senden und Empfangen von Nachrichten hinzu. Öffnen Sie js/public/controllers.js
und ersetzen Sie den Inhalt durch Folgendes:
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 = '';
};
}
Diese App verfügt über nur eine Ansicht. Daher können wir die Weiterleitung aus public/js/app.js
entfernen und vereinfachen:
// Declare app level module which depends on filters, and services
var app = angular.module('myApp', ['myApp.filters', 'myApp.directives']);
Server schreiben
Öffnen Sie routes/socket.js
. Wir müssen ein -Objekt zur Verwaltung des Serverstatus definieren, damit die Nutzernamen eindeutig sind.
// 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
};
}());
Dies definiert im Grunde eine Reihe von Namen, aber mit APIs, die für die Domain eines Chat-Servers sinnvoller sind. Wir verbinden dies mit dem Socket des Servers, um auf die Aufrufe zu antworten, die unser Client ausführt:
// 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);
});
};
Und damit die Bewerbung vollständig ist. Probieren Sie es aus, indem Sie node app.js
ausführen. Die Anwendung sollte dank Socket.IO in Echtzeit aktualisiert werden.
Fazit
Es gibt noch viel mehr, das Sie zu dieser Instant Messaging-App hinzufügen könnten. Sie können zum Beispiel leere Nachrichten senden. Um dies auf Clientseite zu verhindern, können Sie ng-valid
verwenden und den Server prüfen. Vielleicht könnte der Server den aktuellen Nachrichtenverlauf speichern, um neue Nutzer in der App zu erreichen.
Das Schreiben von AngularJS-Apps unter Verwendung anderer Bibliotheken ist einfach, wenn Sie wissen, wie Sie sie in einen Dienst einbinden und Angular darüber informieren, dass ein Modell geändert wurde. Als Nächstes werde ich AngularJS mit der beliebten Visualisierungsbibliothek D3.js verwenden.
Verweise
Angular Socket.IO Seed Fertige Instant Messaging-App AngularJS Express Socket.IO