使用 Socket.IO 编写 AngularJS 应用程序

发布于 2022-04-14 12:51:05 字数 10012 浏览 1134 评论 0

AngularJS 是一个很棒的 JavaScript 框架,它为您提供易于使用和快速的双向数据绑定,一个强大的指令系统,让您可以使用创建可重用的自定义组件,以及更多。 Socket.IO 是一个跨浏览器的 websockets 包装器和 polyfill,使开发实时应用程序变得轻而易举。 顺便说一句,两人合作得很好!

我之前写过关于 使用 Express 编写 AngularJS 应用程序的文章 ,但这次我将写关于如何集成 Socket.IO 以向 AngularJS 应用程序添加实时功能。 在本教程中,我将介绍如何编写一个即时消息应用程序。 这建立在我之前的教程(在服务器上使用类似的 node.js 堆栈)的基础上,因此如果您不熟悉 Node.js 或 Express,我建议您先检查一下。

与往常一样,您可以 在 Github 上获得成品

先决条件

设置 Socket.IO 并将其与 Express 集成有一些样板,所以我创建了 Angular Socket.IO Seed

首先,您可以从 Github 克隆 angular-node-seed 存储库:

git clone git://github.com/btford/angular-socket-io-seed my-project

download it as a zip.

获得种子后,您需要使用 npm 获取一些依赖项。 打开一个终端到带有种子的目录,然后运行:

npm install

安装这些依赖项后,您可以运行骨架应用程序:

node app.js

并在您的浏览器中查看 http://localhost:3000以确保种子按预期工作。

决定应用程序功能

编写聊天应用程序有多种不同的方法,所以让我们描述一下我们将拥有的最小功能。 所有用户都将属于一个聊天室。 用户可以选择和更改他们的名称,但名称必须是唯一的。 服务器将强制执行此唯一性并在用户更改名称时通知。 客户端应该公开一个消息列表,以及当前在聊天室中的用户列表。

一个简单的前端

有了这个规范,我们可以用 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 的 依赖注入系统中 。 因此,我们将首先编写一个服务来包装 socketSocket.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 它需要检查应用程序的状态,并在运行传递给它的回调后更新模板是否有变化。 在内部, $http以同样的方式工作; 在一些 XHR 返回后,它调用 $scope.$apply,以便 AngularJS 可以相应地更新其视图。

请注意,此服务不包含整个 Socket.IO API(留给读者作为练习 ;P )。 但是,它涵盖了本教程中使用的方法,如果您想对其进行扩展,它应该为您指明正确的方向。 我可能会重新考虑编写一个完整的包装器,但这超出了本教程的范围。

现在,在我们的控制器中,我们可以请求 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以防止 在客户端发生这种情况,并在服务器上进行检查。 也许服务器可以保留最近的消息历史记录,以便新用户加入该应用程序。

一旦您了解了如何将它们包装在服务中并通知 Angular 模型已更改,就可以轻松编写使用其他库的 AngularJS 应用程序。 接下来,我计划将 AngularJS 与 D3.js 流行的可视化库。

References

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

拍不死你

暂无简介

0 文章
0 评论
600 人气
更多

推荐作者

已经忘了多久

文章 0 评论 0

15867725375

文章 0 评论 0

LonelySnow

文章 0 评论 0

走过海棠暮

文章 0 评论 0

轻许诺言

文章 0 评论 0

信馬由缰

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文