首页 web前端 js教程 AngularJS+Node.js实现在线聊天室_AngularJS

AngularJS+Node.js实现在线聊天室_AngularJS

May 16, 2016 pm 03:41 PM
angularjs node.js 在线聊天室

不得不说,上手AngularJS比我想象得难多了,把官网提供的PhoneCat例子看完,又跑到慕课网把大漠穷秋的AngularJS实战系列看了一遍,对于基本的使用依然有很多说不清道不明的疑惑,于是决定通过做一个在线聊天室帮助理解。DEMO可以戳→chat room,代码可以戳→ChatRoom-AngularJS

清晰图可以戳 http://files.jb51.net/file_images/article/201508/201508281040051.gif

功能

着手开发之前,首先明确一下需要实现的功能:

新用户登入,广播通知其他用户
用户下线,广播通知其他用户
可显示在线人数及列表
可群聊,可私信
用户若发送群消息,广播通知其他所有用户
用户若发送私信,单独通知收方界面

因为自己是个审美渣,所以全靠bootstrap了,另外还模仿了下微信聊天记录里的气泡设计。

界面分左右两个板块,分别用于显示在线列表和聊天内容。

在左侧的在线列表中,点击不同项可以切换右侧板块的聊天对象。

右侧显示与当前聊天对象的对话记录,不过仅显示最近的30条。每一条聊天记录内容包括发送人的昵称及头像、发送时间、消息内容。关于头像,这里做简单处理,用填充了随机色的方块代替。另外,自己发出去的消息与收到的消息样式自然要做不同设计,所有效果可以看下图。

清晰图可以戳 http://files.jb51.net/file_images/article/201508/201508281040052.png

服务端

服务端我们用Node.js以及混入expresssocket.io来开发,在程序根目录打开终端,执行:

复制代码 代码如下:
npm init

根据提示,生成一个package.json文件。打开并配置依赖项:

 "dependencies": {
  "express": "^4.13.3",
  "socket.io": "^1.3.6"
 }
登录后复制

之后执行 npm install 安装依赖模块。

接下来,我们在根目录下新建app.js,在其中写Server端代码。再新建public文件夹,存放client端代码。

app.js中主要内容如下:

var express = require('express');
var app = require('express')();
var http = require('http').createServer(app);
var io = require('socket.io')(http);

app.use(express.static(__dirname + '/public'));


app.get('/', function (req, res) {
  res.sendfile('index.html');
});


io.on('connection',function(socket){
  socket.on('addUser',function(data){ //有新用户进入聊天室
  });

  socket.on('addMessage',function(data){ //有用户发送新消息
  });
  
  socket.on('disconnect', function () { //有用户退出聊天室
  );
});

http.listen(3002, function () {
  console.log('listening on *:3002');
});
登录后复制

在上面的代码中,我们为以下事件添加了监听:

-addUser,有新用户进入聊天室

该事件由客户端输入昵称后触发,服务端收到后对昵称是否已存在进行判断,如果已存在,通知客户端昵称无效:

复制代码 代码如下:
socket.emit('userAddingResult',{result:false});

反之,通知客户端昵称有效以及当前所有已连接的用户信息,并把新用户信息广播给其他已连接用户:

socket.emit('userAddingResult',{result:true});
allUsers.push(data);//allUsers保存了所有用户
socket.emit('allUser',allUsers);//将所有在线用户发给新用户
socket.broadcast.emit('userAdded',data);//广播欢迎新用户,除新用户外都可看到
登录后复制

其中需要注意'socket.emit'与'socket.broadcast.emit'的区别,可以查看这篇博文socket.io emit的几种用法解释

// send to current request socket client
socket.emit('message', "this is a test");
// sending to all clients except sender
socket.broadcast.emit('message', "this is a test");

登录后复制

-addMessage,有用户发送新消息

在此事件监听里,需要分成两类情况处理:

1.私信
如果消息是发给特定用户A,那么就需要获取A对应的socket实例,然后调用其emit方法。所以每当一个客户端连接到Server端时,我们得把其socket实例保存起来,以备后续之需。

复制代码 代码如下:
connectedSockets[nickname]=socket;//以昵称作下标,保存每个socket实例,发私信需要用

需要发私信时,取出socket实例做操作即可:

复制代码 代码如下:
connectedSockets[nickname].emit('messageAdded',data)

2.群发
群发就比较简单了,用broadcast方法即可:

复制代码 代码如下:
socket.broadcast.emit('messageAdded',data);//广播消息,除原发送者外都可看到

-disconnect,有用户退出聊天室
需要做三件事情:

1.通知其他用户“某用户下线”

复制代码 代码如下:
socket.broadcast.emit('userRemoved', data);

2.将用户从保存了所有用户的数组中移除

3.将其socket实例从保存了所有客户端socket实例的数组中移除

复制代码 代码如下:
delete connectedSockets[nickname]; //删除对应的socket实例

运行一下服务端代码,观察有无错误:

复制代码 代码如下:
node app.js

若没什么问题,继续编写客户端的代码。

客户端

在public目录下新建'index.html',客户端需要用到bootstrap、angularjs、socket.io、jQuery以及我们自己的js和css文件,先把这些文件用标签引入。

<!DOCTYPE html>
<html>
<head lang="en">
  <meta charset="UTF-8">
  <title></title>
  <link href="http://cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
  <link rel="stylesheet" href="./assets/style/app.css"/>
  <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
  <script src="/socket.io/socket.io.js"></script>
  <script src="//cdn.bootcss.com/angular.js/1.4.3/angular.min.js"></script>
  <script src="./assets/js/app.js"></script>
</head>
<body></body>
</html>
登录后复制

我们并不立即深入逻辑细节,把框架搭好先。
首先,在body上加上ng-app属性,标记一下angularjs的“管辖范围”。这个练习中我们只用到了一个控制器,同样将ng-controller属性加到body标签。

复制代码 代码如下:
<body ng-app="chatRoom" ng-controller="chatCtrl">

接下来在js中,我们来创建module及controller。

var app=angular.module("chatRoom",[]);
app.controller("chatCtrl",['$scope','socket','randomColor',function($scope,socket,randomColor){}]);
登录后复制

注意这里,我们用内联注入添加了socket和randomColor服务依赖。这里我们不用推断式注入,以防部署的时候用uglify或其他工具进行了混淆,变量经过了重命名导致注入失效。
在这个练习中,我们自定义了两个服务,socket和randomColor,前者是对socket.io的包装,让其事件进入angular context,后者是个可以生成随机色的服务,用来给头像指定颜色。

//socket服务
app.factory('socket', function($rootScope) {
  var socket = io(); //默认连接部署网站的服务器
  return {
    on: function(eventName, callback) {...},
    emit: function(eventName, data, callback) {...}
  };
});

//randomcolor服务
app.factory('randomColor', function($rootScope) {
  return {
    newColor: function() {
      return '#'+('00000'+(Math.random()*0x1000000<<0).toString(16)).slice(-6);//返回一个随机色
    }
  };
});
登录后复制

注意socket服务中连接的语句“var socket = io();”,我们并没有传入任何url,是因为其默认连接部署这个网站的服务器。

考虑到聊天记录以及在线人员列表都是一个个逻辑及结构重复的条目,且html结构较复杂,为了其复用性,我们把它们封装成两个指令:

app.directive('message', ['$timeout',function($timeout) {}])
  .directive('user', ['$timeout',function($timeout) {}]);
登录后复制

注意这里两个指令都注入了'$timeout'依赖,其作用后文会解释。

这样一个外层框架就搭好了,现在我们来完成内部的细节。

登录

页面刚加载时只显示登录界面,只有当输入昵称提交后且收到服务端通知昵称有效方可跳转到聊天室。我们将ng-show指令添加到登录界面和聊天室各自的dom节点上,来帮助我们显示或隐藏元素。用'hasLogined'的值控制是显示或隐藏。

<!-- chat room -->
<div class="chat-room-wrapper" ng-show="hasLogined">
...
</div>
<!-- end of chat room -->

<!-- login form -->
<div class="userform-wrapper" ng-show="!hasLogined">
...
</div>
<!-- end of login form -->
登录后复制

JS部分

 $scope.login = function() { //登录
   socket.emit("addUser", {...});
 }

 //收到登录结果
 socket.on('userAddingResult', function(data) {
   if (data.result) {
     $scope.hasLogined = true;
   } else { //昵称被占用
     $scope.hasLogined = false;
   }
 });
登录后复制

这里监听了socket连接上的'userAddingResult'事件,接收服务端的通知,确认是否登录成功。

socket连接监听

成功登录以后,我们还监听socket连接上的其他事件:

复制代码 代码如下:

//接收到欢迎新用户消息,显示系统欢迎辞,刷新在线列表<br /> socket.on('userAdded', function(data) {});<br /> //接收到所有用户信息,初始化在线列表<br /> socket.on('allUser', function(data) {});<br /> //接收到用户退出消息,刷新在线列表<br /> socket.on('userRemoved', function(data) {});<br /> //接收到新消息,添加到聊天记录<br /> socket.on('messageAdded', function(data) {});<br />

接收到事件以后,做相应的刷新动作,这里的socket是socket.io经过包装的服务,内部仅包装了我们需要用到的两个函数on和emit。我们在事件监听里对model做的修改,都会在AngularJS内部得到通知和处理,UI才会得到及时刷新。
监听内做的事情太具体和琐碎了,这里就不列出了,接下来介绍一下message指令。

message 指令

最后分享一下我在写message指令时遇到的问题。首先看一下其代码:

app.directive('message', ['$timeout',function($timeout) {
  return {
    restrict: 'E',
    templateUrl: 'message.html',
    scope:{
      info:"=",
      self:"=",
      scrolltothis:"&"
    },
    link:function(scope, elem, attrs){
        $timeout(scope.scrolltothis);
    }
  };
}])
登录后复制

以及其模板message.html:

<div ng-switch on="info.type">
  <!-- 欢迎消息 -->
  <div class="system-notification" ng-switch-when="welcome">系统{{info.text}}来啦,大家不要放过他~</div>
  <!-- 退出消息 -->
  <div class="system-notification" ng-switch-when="bye">系统:byebye,{{info.text}}</div>
  <!-- 普通消息 -->
  <div class="normal-message" ng-switch-when="normal" ng-class="{others:self!==info.from,self:self===info.from}">
    <div class="name-wrapper">{{info.from}} @ {{time | date: 'HH:mm:ss' }}</div>
    <div class="content-wrapper">{{info.text}}<span class="avatar"></span></div>
  </div>
</div>
登录后复制

模板中我们用ng-switch指令监听info.type变量的值,根据其值的不同显示不同内容。比如,当info.type值为"welcome"时,创建第一个dom节点,删除下方另外两个div。
另外,普通消息下,为了在UI上区分自己发出去的和收到的消息,需要给他们应用不同的样式,这里用ng-class指令实现。

复制代码 代码如下:
ng-class="{others:self!==info.from,self:self===info.from}"

当'self===info.from'返回true时,应用'self'类,否则,应用'others'类。
在此指令中,我们创建了独立作用域,并绑定了三个属性,绑定完后还必须在父作用域的HTML标签上添加相应属性。

scope:{
    info:"=",
    self:"=",
    scrolltothis:"&"
}

<message self="nickname" scrolltothis="scrollToBottom()" info="message" ng-repeat="message in messages"></message>
登录后复制

在link函数中,执行一个动作:每当一个message被加到页面上时,将聊天记录滚动到最下方,一开始我是这样写的:

复制代码 代码如下:
link:function(scope, elem, attrs){ scope.scrolltothis();}

结果发生了一个很奇怪的现象,总是滚动到上一条位置,而不是最新这条。调试之后发现是因为'scrolltothis'函数执行的时候,DOM还没渲染,所以在函数内部获取scrollHeight的时候获得的总是添加DOM节点之前的状态。这时候,可以把代码放到$timeout里延迟0秒执行,延迟0秒并不意味着会立即执行,因为js的单线程特性,代码实际会等到dom渲染完再执行。

复制代码 代码如下:
$timeout(scope.scrolltothis);

完整代码可以戳我的GitHub→ChatRoom-AngularJS,DEMO可以戳→chat room

有任何不妥之处或错误欢迎各位指出,不胜感激~

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1664
14
CakePHP 教程
1422
52
Laravel 教程
1316
25
PHP教程
1267
29
C# 教程
1239
24
图文详解Node V8引擎的内存和GC 图文详解Node V8引擎的内存和GC Mar 29, 2023 pm 06:02 PM

本篇文章带大家深入了解NodeJS V8引擎的内存和垃圾回收器(GC),希望对大家有所帮助!

一文聊聊Node中的内存控制 一文聊聊Node中的内存控制 Apr 26, 2023 pm 05:37 PM

基于无阻塞、事件驱动建立的Node服务,具有内存消耗低的优点,非常适合处理海量的网络请求。在海量请求的前提下,就需要考虑“内存控制”的相关问题了。 1. V8的垃圾回收机制与内存限制 Js由垃圾回收机

聊聊如何选择一个最好的Node.js Docker镜像? 聊聊如何选择一个最好的Node.js Docker镜像? Dec 13, 2022 pm 08:00 PM

选择一个Node​的Docker镜像看起来像是一件小事,但是镜像的大小和潜在漏洞可能会对你的CI/CD流程和安全造成重大的影响。那我们如何选择一个最好Node.js Docker镜像呢?

如何使用Go语言和Redis实现在线聊天室 如何使用Go语言和Redis实现在线聊天室 Oct 27, 2023 pm 03:28 PM

如何使用Go语言和Redis实现在线聊天室引言:随着互联网的迅速发展,社交网络已经成为人们日常生活中不可或缺的一部分。在线聊天室作为社交网络中的一个重要组成部分,具有便捷、实时、交互性强等特点受到人们的欢迎。本文以Go语言和Redis为基础,介绍如何使用这两个工具实现一个简单的在线聊天室。一、Go语言介绍:Go语言是一门开源的、面向现代化操作系统的系统编程语

深入聊聊Node中的File模块 深入聊聊Node中的File模块 Apr 24, 2023 pm 05:49 PM

文件模块是对底层文件操作的封装,例如文件读写/打开关闭/删除添加等等 文件模块最大的特点就是所有的方法都提供的**同步**和**异步**两个版本,具有 sync 后缀的方法都是同步方法,没有的都是异

一起聊聊Node中的事件循环 一起聊聊Node中的事件循环 Apr 11, 2023 pm 07:08 PM

事件循环是 Node.js 的基本组成部分,通过确保主线程不被阻塞来实现异步编程,了解事件循环对构建高效应用程序至关重要。下面本篇文章就来带大家深入了解Node中的事件循环 ,希望对大家有所帮助!

聊聊用pkg将Node.js项目打包为可执行文件的方法 聊聊用pkg将Node.js项目打包为可执行文件的方法 Dec 02, 2022 pm 09:06 PM

​如何用pkg打包nodejs可执行文件?下面本篇文章给大家介绍一下使用pkg将Node项目打包为可执行文件的方法,希望对大家有所帮助!

node无法用npm命令怎么办 node无法用npm命令怎么办 Feb 08, 2023 am 10:09 AM

node无法用npm命令是因为没有正确配置环境变量,其解决办法是:1、打开“系统属性”;2、找到“环境变量”->“系统变量”,然后编辑环境变量;3、找到nodejs所在的文件夹;4、点击“确定”即可。

See all articles