Недавно я создал простой чат, используя исключительно TypeScript. Главной целью этого проекта было написание приложения, демонстрирующего использование этого языка и на клиенте, и на сервере. Клиентская часть чата основана на свежей версии Angular. Сервер базируется на Node.js. Взаимодействие между ними организовано с помощью протокола WebSocket.
Из этого материала вы узнаете о том, как создать чат, о котором идёт речь, с нуля. Вот, кстати, как выглядит работа с ним.

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

Обмен данными с использованием протокола WebSocket
Код, имеющий отношение к клиентской и серверной частям приложения, мы разместим в отдельных папках. Ниже показана структура готового приложения. Подробности рассмотрим ниже, когда будем говорить о ключевых файлах нашего проекта.
Так как протокол WebSocket — это спецификация, можно найти несколько её практических реализаций. Тут можно использоватьJavaScript, TypeScript, или любой другой язык программирования.
В данном случае мы применим библиотеку Socket.IO. Это — одна из самых быстрых и надёжных библиотек, реализующая возможности обмена данными в реальном времени.
TypeScript предлагает программисту замечательные возможности, команда разработчиков поддерживает язык в актуальном состоянии. Кроме того, использование типизации позволяет сократить число ошибок в коде, в сравнении с использованием обычного JS. По мне, так этих причин вполне достаточно для использования TS на сервере.
Создадим файл
Кроме того, понадобится установить некоторые зависимости разработки для того, чтобы интегрировать в проект
Создадим файл
Пользуясь возможностями статической типизации, создадим небольшую модель данных:
Взглянем на структуру директории
Главные файлы в директории
Вот код файла
Вот файл
Код, приведённый выше, даёт следующие классы и взаимоотношения между ними:

Диаграмма классов сервера
Для того чтобы получить JavaScript-файлы, необходимые движку V8, на котором основан Node.js, добавим задачу
Как видите, выходные данные процесса сборки (JS-файлы) будут расположены в директории
Теперь, для того, чтобы запустить сервер, нужно воспользоваться такой командой:
Создадим папку для клиента с использованием Angular CLI:
Установим зависимости проекта. Тут можно выполнить команду
Для того чтобы воспользоваться набором компонентов Angular Material в проекте, который создан с помощью Angular CLI, посмотрите свежее руководство на material.angular.io и действуйте в соответствии с ним.
В соответствии с рекомендациями по структуре Angular-проектов, создадим модули
Сделать это можно из командной строки:
Для того чтобы оценить взаимоотношения между этими модулями, проанализируйте файлы
Теперь нужно подключить к нашему клиентскому приложению модули
Прежде чем создавать компоненты для чата, создадим новый модуль:
Теперь добавим в этот модуль компонент:
Для того чтобы использовать веб-сокеты и собственные модели, создадим ещё одну папку
В результате должна получиться следующая структура:
Так как наше Angular-приложение поддерживает RxJS, для работы с событиями Socket.IO можно использовать наблюдаемые объекты. Поэтому файл
Теперь мы готовы к тому, чтобы реагировать на сообщения с сервера, поэтому рассмотрим файл
Как только
Функции
Из этого материала вы узнали о том, как, пользуясь TypeScript, написать приложение реального времени — чат, в котором TS применяется и на клиенте, и на сервере, и в котором задействованы такие технологии, как WebSockets, Node.js и Angular. Исходный код проекта можно найти здесь. А вот — работающий чат (откройте в браузере пару вкладок с этой страницей для того, чтобы его испытать).
Уважаемые читатели! Пользуетесь ли вы TypeScript для разработки серверных приложений?

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

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

Обмен данными с использованием протокола WebSocket
Структура приложения
Код, имеющий отношение к клиентской и серверной частям приложения, мы разместим в отдельных папках. Ниже показана структура готового приложения. Подробности рассмотрим ниже, когда будем говорить о ключевых файлах нашего проекта.
server/
|- src/
|- package.json
|- tsconfig.json
|- gulpfile.js
client/
|- src/
|- package.json
|- tsconfig.json
|- .angular-cli.json
Выбор реализации WebSocket
Так как протокол WebSocket — это спецификация, можно найти несколько её практических реализаций. Тут можно использовать
В данном случае мы применим библиотеку 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 для разработки серверных приложений?
