Задача заключается в создании многопользовательской карточной онлайн-игры в «очко», где игрокам поочередно предлагается брать карты. Выигрывает тот, кто набрал больше очков но не более чем 21. При выигрыше, сумма ставки прибавляется к счету игрока, который победил и отнимается у того, кто проиграл.
Наше приложение будет состоять из 2-х частей: серверной и клиентской. Создадим каркас приложения ангуляр для клиентской части. Для этого воспользуемся библиотекой командной строки angular-cli, которую желательно установить глобально в систему.
sudo npm install -g @angular/cli
Итак, создаем приложение следующим образом:
ng new client
При установке соглашаемся на использование роутинга, он нам пригодится. После чего получим каталог client с необходимой структурой папок. Нас больше всего будет интересовать каталог src/app, в котором и будет код нашего приложения. Игра будет содержать три роутинга и соответственно – три страницы и привязанных к ним компонента.
- Страница с формой авторизации.
- Страница со списком пользователей онлайн.
- Станица комнаты с игрой между двумя пользователями.
Поэтому создадим эти три компонента следующими командами:
ng g c login;
ng g c online;
ng g c room.
Вот так должен выглядеть результат, при котором создались 3 одноименных каталога с компонентами, которые также были включены в корневой модуль app.module.ts, о чем свидетельствует строка:
UPDATE src/app/app.module.ts
Если мы откроем этот файл, то заметим что в него добавился импорт каждого компонента и включен в секцию декларации.
import { LoginComponent } from './login/login.component';
import { OnlineComponent } from './online/online.component';
import { RoomComponent } from './room/room.component';
@NgModule({
declarations: [
AppComponent,
LoginComponent,
OnlineComponent,
RoomComponent
],
Привяжем эти компоненты к маршрутизатору, создав 3 маршрута (на самом деле их 4 но один используется для редиректа).
import { LoginComponent } from './login/login.component';
import { OnlineComponent } from './online/online.component';
import { RoomComponent } from './room/room.component';
const routes: Routes = [
{
path: '',
redirectTo: 'login',
pathMatch: 'full'
},
{
path: 'login',
component: LoginComponent
},
{
path: 'online',
component: OnlineComponent
},
{
path: 'room/:uuid',
component: RoomComponent
}
];
В первом роутинге мы делаем редирект на форму авторизации (LoginComponent) на главной странице. Т.е. теперь, когда мы запустим сервер разработки командой:
ng serve
Потом, когда перейдем по адресу http://localhost:4200 нас автоматически перебросит на адрес http://localhost:4200/login и мы увидим такую картину:
В последнем роутинге мы определили переменную :uuid при указании шаблона пути, эта переменная будет изменятся и содержать уникальный идентификатор игровой комнаты.
Теперь осталось открыть шаблон корневого компонента app.component.html, убрать все лишнее от приветствия и вставить ссылки на каждый роут вот так:
Принято использовать специальную директиву [routerLink] для указания пути роутинга. Вместо параметра :uuid в роутинге игровой комнаты мы пока вставим тестовое значение room_id. В теге router-outlet будет выводится содержимое шаблонов компонентов, соответствующих пути роута.
Вот что должно получится:
Теперь предлагаю оформить интерфейс в лучших традициях bootstrap и подключить библиотеку ngx-bootstrap такой командой:
ng add ngx-bootstrap
Далее необходимо перезагрузить сервер разработки и применить в шаблоне разметку bootstrap.
Вот что у нас получилось:
Создадим форму для логина в шаблоне login/login.component.html.
Теперь необходимо привязать сабмит формы к методу компонента. Для этого откроем код компонента login/login.component.ts и добавим переменную с логином и метод.
export class LoginComponent implements OnInit {
login: string;
constructor() { }
ngOnInit() {
}
doLogin(){
console.log(this.login);
}
}
Для проверки в методе doLogin мы выводим в консоль значение переменной login, объявленной в классе перед конструктором.
Эта переменная будет связана со значением поля input в шаблоне следующим образом:
Обратите внимание на обязательное присутствие атрибута name в теге input. Однако, для связывание переменной login директивой [(ngModel)] необходимо подключить модуль FormsModule в корневом модуле, иначе вы увидите следующую ошибку в консоле браузера:
Can't bind to 'ngModel' since it isn't a known property of 'input'.
Поэтому импортируем этот модуль и включим в секцию imports корневого модуля app.module.ts.
import from '@angular/forms';
@NgModule({
imports: [
...
FormsModule
],
Осталось вызвать метод doLogin при сабмите нашей формы.
Результат работы:
Теперь нам предстоит задействовать веб-сокеты и по ним передать логин пользователя на сервер. Мы будем использовать библиотеку socket.io, и для нее есть обертка под Ангуляр, устанавливаем.
npm i ngx-socket-io --save
Далее ее необходимо импортировать в корневом модуле приложения, сконфигурировать и добавить в секцию imports.
import { SocketIoModule, SocketIoConfig } from 'ngx-socket-io';
const config: SocketIoConfig = { url: 'http://localhost:3000', options: {} };
@NgModule({
imports: [
...
SocketIoModule.forRoot(config)
],
Как видно, мы планируем запустить сокет-сервер на порту 3000 локальной машины.
Но соединение не будет инициировано, пока мы не создадим сервис, его использующий, и не включим его в конструктор компонента. Для таких целей рекомендуется использовать именно сервисы, чтобы не загромождать компоненты. Сервисы представляют собой классы, экземпляры которых создаются в конструкторах компонентов путем механизма внедрения зависимостей Ангуляра, но это отдельная тема.
А пока создадим такой сервис в новом файле socket.service.ts.
import { Injectable } from '@angular/core';
import { Socket } from 'ngx-socket-io';
@Injectable({
providedIn: 'root'
})
export class SocketService {
constructor(private socket: Socket) { }
sockLogin(login: string) {
console.log(`emmiting login $ to server`);
this.socket.emit('LoginEvent', );
}
}
Декоратором @Injectable мы делаем сервис способным к применению внутри компонентов. Функцией emit объекта socket мы передаем информацию на сервер.
Применим этот сервис в компоненте, пока просто импортировав его класс и добавив приватную переменную socket_service в конструктор класса компонента LoginComponent:
import { SocketService } from './../socket.service';
...
export class LoginComponent implements OnInit {
constructor(private socket_service: SocketService) { }
...
}
После этого наш компонент попытается создать сокет-соединение с сервером, которого пока еще нет. Об этом будет свидетельствовать сообщение об ошибке в консоле.
Причем попытки соединится продолжаются не прекращаясь, это позволяет восстанавливать сокет-соединение при падении сервера.
Но несмотря на то, что рабочего сервера пока нет, все же реализуем отправку сообщения, вызвав метод sockLogin нашего сервиса из компонента LoginComponent, и передадим в нее переменную login.
doLogin(){
this.socket_service.sockLogin(this.login);
}
Выводы
В этой статье рассмотрен процесс создания приложения Angular с использованием системы маршрутизации. Также описан процесс работы с компонентами, их шаблонами и сервисами на примере формы авторизации. Затронуто использование сокет-соединения на базе библиотеки ngx-socket-io.
В следующей статье будет рассмотрен процесс создания серверной части и взаимодействие его с клиентской.