Приложения реального времени на TypeScript: разработка чата с применением WebSocket, Node и Angular

https://medium.com/dailyjs/real-time-apps-with-typescript-integrating-web-sockets-node-angular-e2b57cbd1ec1
  • Перевод
Недавно я создал простой чат, используя исключительно TypeScript. Главной целью этого проекта было написание приложения, демонстрирующего использование этого языка и на клиенте, и на сервере. Клиентская часть чата основана на свежей версии Angular. Сервер базируется на Node.js. Взаимодействие между ними организовано с помощью протокола WebSocket.

Из этого материала вы узнаете о том, как создать чат, о котором идёт речь, с нуля. Вот, кстати, как выглядит работа с ним.


Чат, написанный на TypeScript

О приложениях реального времени


В соответствии с этим определением из Википедии, приложение реального времени позволяет заинтересованным сущностям получать информацию сразу же после того, как она была опубликована, без необходимости периодического опроса источника информации. Таким образом, эта разновидность приложений должна давать пользователям ощущение того, что некие действия происходят мгновенно, без задержек.

Протокол WebSocket


WebSocket — это протокол, который позволяет организовать двунаправленный канал передачи данных. В нашем случае это означает, что браузер и веб-сервер могут поддерживать связь в реальном времени, отправляя друг другу сообщения при существовании открытого соединения между ними.


Обмен данными с использованием протокола WebSocket

Структура приложения


Код, имеющий отношение к клиентской и серверной частям приложения, мы разместим в отдельных папках. Ниже показана структура готового приложения. Подробности рассмотрим ниже, когда будем говорить о ключевых файлах нашего проекта.

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

Выбор реализации WebSocket


Так как протокол WebSocket — это спецификация, можно найти несколько её практических реализаций. Тут можно использовать JavaScript, TypeScript, или любой другой язык программирования.

В данном случае мы применим библиотеку Socket.IO. Это — одна из самых быстрых и надёжных библиотек, реализующая возможности обмена данными в реальном времени.

Зачем использовать TypeScript на сервере?


TypeScript предлагает программисту замечательные возможности, команда разработчиков поддерживает язык в актуальном состоянии. Кроме того, использование типизации позволяет сократить число ошибок в коде, в сравнении с использованием обычного JS. По мне, так этих причин вполне достаточно для использования TS на сервере.

Инициализация серверного приложения


Создадим файл package.json и установим следующие зависимости:

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

Кроме того, понадобится установить некоторые зависимости разработки для того, чтобы интегрировать в проект gulp и typescript, всё это пригодится нам в ходе создания и сборки готового проекта:

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.ts и chat-server.ts. Первый позволяет создавать и экспортировать приложение ChatServer, в то время как второй содержит конфигурации express и Socket.IO:

Вот код файла index.js:

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

let app = new ChatServer().getApp();
export { app };

Вот файл chat-server.ts:

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;
    }
}

Классы сервера


Код, приведённый выше, даёт следующие классы и взаимоотношения между ними:


Диаграмма классов сервера

Сборка и запуск сервера


Для того чтобы получить JavaScript-файлы, необходимые движку V8, на котором основан Node.js, добавим задачу build в файл gulpfile.js:

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"));
});

Как видите, выходные данные процесса сборки (JS-файлы) будут расположены в директории dist. Для того, чтобы выполнить сборку, понадобится следующая команда:

gulp build

Теперь, для того, чтобы запустить сервер, нужно воспользоваться такой командой:

node dist/index.js

Разработка клиента чата


Создадим папку для клиента с использованием Angular CLI:

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

Установим зависимости проекта. Тут можно выполнить команду npm install, но я предпочитаю использовать на данном шаге Yarn:

cd typescript-chat-client
yarn install

Добавление в проект набора компонентов Angular Material


Для того чтобы воспользоваться набором компонентов Angular Material в проекте, который создан с помощью Angular CLI, посмотрите свежее руководство на material.angular.io и действуйте в соответствии с ним.

В соответствии с рекомендациями по структуре Angular-проектов, создадим модули shared и material:

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.ts и shared.module.ts.

Подключение express и Socket.IO


Теперь нужно подключить к нашему клиентскому приложению модули express и socket.io:

npm install express socket.io --save

Модули и компоненты чата


Прежде чем создавать компоненты для чата, создадим новый модуль:

ng generate module chat --module app

Теперь добавим в этот модуль компонент:

ng generate component chat --module chat

Для того чтобы использовать веб-сокеты и собственные модели, создадим ещё одну папку shared. В этот раз — внутри директории chat:

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

Наблюдаемые объекты и веб-сокеты


Так как наше Angular-приложение поддерживает RxJS, для работы с событиями Socket.IO можно использовать наблюдаемые объекты. Поэтому файл socket.services.ts будет выглядеть так:

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());
        });
    }
}

Теперь мы готовы к тому, чтобы реагировать на сообщения с сервера, поэтому рассмотрим файл chat.component.ts (тут опущен код, касающийся Material и событий пользовательского интерфейса):

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);
  }
}

Как только ChatComponent инициализируется, компонент подпишется на наблюдаемые объекты SocketService для того, чтобы получать события, связанные с подключением или входящие сообщения.

Функции sendMessage и sendNotification будут отправлять, соответственно, сообщения и уведомления через один и тот же сервис. Уведомления используются для оповещения системы о том, что к чату присоединился новый пользователь и для переименования участников чата.

Итоги


Из этого материала вы узнали о том, как, пользуясь TypeScript, написать приложение реального времени — чат, в котором TS применяется и на клиенте, и на сервере, и в котором задействованы такие технологии, как WebSockets, Node.js и Angular. Исходный код проекта можно найти здесь. А вот — работающий чат (откройте в браузере пару вкладок с этой страницей для того, чтобы его испытать).

Уважаемые читатели! Пользуетесь ли вы TypeScript для разработки серверных приложений?

  • +13
  • 10,6k
  • 3
RUVDS.com 650,93
RUVDS – хостинг VDS/VPS серверов
Поделиться публикацией
Комментарии 3
    –2
    graphql больше бы подошел, чем socket.io
      0
      Точно, давайте ради простых сообщений прикрутим огромную лабуду фреймворк.
      0
      Обоснуйте в чем бы он больше подошел. Как бы, например, вы красиво организовали рассылку сообщений с сервера подключенным клиентам?

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое