首页
Preview

使用 TypeScript 开发实时应用程序:集成 Web Sockets、Node 和 Angular

前段时间,我使用 TypeScript 语言实现了一个简单的聊天应用程序。主要目的是编写一个演示,以解释如何在客户端和服务器上使用这种编程语言。客户端应用程序使用了最新的 Angular 功能。

在本文中,我将向你展示如何从头开始实现该应用程序。

什么是实时应用程序?

根据 维基百科的定义,实时应用程序允许接收到信息一发布就能接收到,而不需要定期检查源以获取更新。因此,这种应用程序应该让用户感觉事件和操作立即发生。

WebSockets

WebSockets 是提供双向通信通道的协议。这意味着浏览器和 Web 服务器可以维护实时通信,在连接打开的同时来回发送消息。

Websockets 通信

应用程序结构

我们将分离与服务器相关的代码和客户端代码。当最重要的文件被解释时,我将进入详细说明。现在,这是我们应用程序的预期结构:

server/
|- src/
|- package.json
|- tsconfig.json
|- gulpfile.js
client/
|- src/
|- package.json
|- tsconfig.json
|- .angular-cli.json

服务器代码

由于 WebSockets 是规范,我们可以找到几个实现。我们可以选择 J̶a̶v̶a̶S̶c̶r̶i̶p̶t̶ TypeScript 或其他编程语言。

在这种情况下,我们将使用 Socket.IO,它是最快和最可靠的实时引擎之一。

为什么在服务器端使用 TypeScript?

TypeScript 具有非常酷的功能,并且经常更新。它可以预防约 15% 的错误。你需要更多的理由吗? 😄

初始化服务器应用程序

创建一个<a class="af of" href="https://docs.npmjs.com/cli/init" rel="noopener ugc nofollow" target="_blank">package.json</a> 文件,然后安装以下依赖项:

npm install --save express socket.io @types/express @types/socket.io

我们需要安装一些 devDependencies,以允许使用 gulptypescript 轻松定义构建任务:

npm install --save-dev typescript gulp gulp-typescript

TypeScript 编译器配置

创建一个 tsconfig.json 文件,内容如下:

{
  "files": [
    "src/*.ts",
    "src/model/*.ts"
  ],
  "compilerOptions": {
    "target": "es5"
  }
}

数据模型定义

利用静态类型,让我们定义一个小型数据模型,如下所示:

export class User {
    constructor(private name: string) {}
}

export class Message {
    constructor(private from: User, private content: string) {}
}

export class ChatMessage extends Message{
    constructor(from: User, content: string) {
        super(from, content);
    }
}

..让我们进一步查看 server/src 目录中的细节:

server/
|- src/
   |- model/
      |- message.model.ts
      |- user.model.ts
   |- index.ts
   |- server.ts
|- package.json
|- tsconfig.json
|- gulpfile.js

聊天服务器实现

server 目录中的主要文件是 index.tschat-server.ts。第一个允许我们创建和导出我们的 ChatServer 应用程序,而最后一个包含 expresssocket.IO 配置:

import { ChatServer } from './chat-server';

let app = new ChatServer().getApp();
export { app };
import { createServer, Server } from 'http';
import * as express from 'express';
import * as socketIo from 'socket.io';

import { Message } from './model';

export class ChatServer {
    public static readonly PORT:number = 8080;
    private app: express.Application;
    private server: Server;
    private io: SocketIO.Server;
    private port: string | number;

    constructor() {
        this.createApp();
        this.config();
        this.createServer();
        this.sockets();
        this.listen();
    }

    private createApp(): void {
        this.app = express();
    }

    private createServer(): void {
        this.server = createServer(this.app);
    }

    private config(): void {
        this.port = process.env.PORT || ChatServer.PORT;
    }

    private sockets(): void {
        this.io = socketIo(this.server);
    }

    private listen(): void {
        this.server.listen(this.port, () => {
            console.log('Running server on port %s', this.port);
        });

        this.io.on('connect', (socket: any) => {
            console.log('Connected client on port %s.', this.port);
            socket.on('message', (m: Message) => {
                console.log('[server](message): %s', JSON.stringify(m));
                this.io.emit('message', m);
            });

            socket.on('disconnect', () => {
                console.log('Client disconnected');
            });
        });
    }

    public getApp(): express.Application {
        return this.app;
    }
}

服务器类

上述代码将得到以下类和关系:

服务器类图

构建和运行服务器

为了拥有 Node.js 的 V8 引擎所需的 JavaScript 文件,我们可以在我们的 gulpfile.js 文件中添加一个 build 任务:

var gulp = require("gulp");
var ts = require("gulp-typescript");
var tsProject = ts.createProject("tsconfig.json");

gulp.task("build", function () {
    return tsProject.src()
        .pipe(tsProject())
        .js.pipe(gulp.dest("./dist"));
});

如你所见,构建过程的输出(JavaScript 文件)将位于 dist 目录中。要执行此操作,你需要运行:

gulp build

现在,我们可以运行 node dist/index.js 命令来运行服务器。

客户端代码

让我们使用最新的 Angular CLI 版本生成我们的 client 目录:

ng new typescript-chat-client --routing --prefix tcc --skip-install

然后运行 npm install(我更喜欢使用 Yarn)安装依赖项:

cd typescript-chat-client
yarn install

添加 Angular Material

查找并按照最新的指南在你的 Angular CLI 项目中安装 Angular Material

作为在我们的项目结构中使用最佳实践的一部分,我们可以创建 sharedmaterial 模块:

client/
|- src/
   |- app/
      |- chat/
      |- shared/
         |- material/
            |- material.module.ts
         |- shared.module.ts
      |-app.module.ts

我们可以从命令行界面执行此操作:

ng generate module shared --module app
ng generate module shared/material --module shared

检查 app.module.tsshared.module.ts 中的更改,以查看这些模块之间创建的关系。

添加 express 和 socket.IO

我们需要将 expresssocket.io 模块添加到我们的客户端应用程序中:

npm install express socket.io --save

聊天模块和组件

在开始创建聊天应用程序的组件之前,让我们创建一个新模块:

ng generate module chat --module app

现在将组件添加到最新的模块中:

ng generate component chat --module chat

为了使用 websockets 和自定义模型,让我们在 chat 目录中创建另一个 shared 文件夹:

ng generate service chat/shared/services/socket --module chat
ng generate class chat/shared/model/user
ng generate class chat/shared/model/message

我们将最终拥有类似于以下结构:

client/
|- src/
   |- app/
      |- chat/
         |- shared/
           |- model/
              |- user.ts
              |- message.ts
           |- services/
              |- socket.service.ts
      |- shared/
      |-app.module.ts

Observables 和 Web Sockets

由于我们的 Angular 应用程序附带了 RxJS,我们可以使用 Observables 来捕获 Socket.IO 事件:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Observer } from 'rxjs/Observer';
import { Message } from '../model/message';
import { Event } from '../model/event';

import * as socketIo from 'socket.io-client';

const SERVER_URL = 'http://localhost:8080';

@Injectable()
export class SocketService {
    private socket;

    public initSocket(): void {
        this.socket = socketIo(SERVER_URL);
    }

    public send(message: Message): void {
        this.socket.emit('message', message);
    }

    public onMessage(): Observable<Message> {
        return new Observable<Message>(observer => {
            this.socket.on('message', (data: Message) => observer.next(data));
        });
    }

    public onEvent(event: Event): Observable<any> {
        return new Observable<Event>(observer => {
            this.socket.on(event, () => observer.next());
        });
    }
}

我们需要定义一些枚举来管理应用程序中的ActionsEvents

// Actions you can take on the App
export enum Action {
    JOINED,
    LEFT,
    RENAME
}

// Socket.io events
export enum Event {
    CONNECT = 'connect',
    DISCONNECT = 'disconnect'
}

现在我们已经准备好从服务器中监听消息了:

import { Component, OnInit } from '@angular/core';

import { Action } from './shared/model/action';
import { Event } from './shared/model/event';
import { Message } from './shared/model/message';
import { User } from './shared/model/user';
import { SocketService } from './shared/services/socket.service';

@Component({
  selector: 'tcc-chat',
  templateUrl: './chat.component.html',
  styleUrls: ['./chat.component.css']
})
export class ChatComponent implements OnInit {
  action = Action;
  user: User;
  messages: Message[] = [];
  messageContent: string;
  ioConnection: any;

  constructor(private socketService: SocketService) { }

  ngOnInit(): void {
    this.initIoConnection();
  }

  private initIoConnection(): void {
    this.socketService.initSocket();

    this.ioConnection = this.socketService.onMessage()
      .subscribe((message: Message) => {
        this.messages.push(message);
      });

    this.socketService.onEvent(Event.CONNECT)
      .subscribe(() => {
        console.log('connected');
      });
      
    this.socketService.onEvent(Event.DISCONNECT)
      .subscribe(() => {
        console.log('disconnected');
      });
  }

  public sendMessage(message: string): void {
    if (!message) {
      return;
    }

    this.socketService.send({
      from: this.user,
      content: message
    });
    this.messageContent = null;
  }

  public sendNotification(params: any, action: Action): void {
    let message: Message;

    if (action === Action.JOINED) {
      message = {
        from: this.user,
        action: action
      }
    } else if (action === Action.RENAME) {
      message = {
        action: action,
        content: {
          username: this.user.name,
          previousUsername: params.previousUsername
        }
      };
    }

    this.socketService.send(message);
  }
}

此文件中省略了 Material 代码和 UI 事件。

一旦 ChatComponent 初始化,该组件将订阅 SocketService 可观察对象,以便开始接收连接事件或传入的消息。

sendMessagesendNotification 函数将通过同一服务发送相应的内容。此时发送的通知是 Rename UserUser Joined

聊天应用程序功能

  • Angular CLI
  • Angular 5
  • Angular Material
  • 表单验证

源代码

在此 GitHub 存储库中找到完整的项目:

github.com/luixaviles/socket-io-typescript-chat

实时演示

转到 typescript-chat.firebaseapp.com,在你最喜欢的浏览器中打开两个或多个选项卡,开始聊天。

译自:https://medium.com/dailyjs/real-time-apps-with-typescript-integrating-web-sockets-node-angular-e2b57cbd1ec1

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
一个人玩
先找到想要的,然后出发

评论(0)

添加评论