Завдання полягає у створенні розрахованої на багато користувачів карткової онлайн-ігри в «очко», де гравцям по черзі пропонується брати карти. Виграє той, хто набрав більше очок але не більше 21. При виграші, сума ставки додається до рахунку гравця, який переміг і віднімається у того, хто програв.
Наш додаток складатиметься з 2-х частин: серверної та клієнтської. Створимо каркас програми ангуляр для клієнтської частини. Для цього скористаємось бібліотекою командного рядка angular-cli, яку бажано встановити глобально до системи.
sudo npm install -g @angular/cli
Отже, створюємо додаток так:
ng new client
При установці погоджуємось на використання роутингу, він стане нам у нагоді. Після цього отримаємо каталог client з необхідною структурою папок. Нас найбільше цікавитиме каталог src/app, в якому і буде код нашої програми. Гра міститиме три роутингу і відповідно – три сторінки та прив'язані до них компоненти.
- Сторінка із формою авторизації.
- Сторінка зі списком користувачів онлайн.
- Станиця кімнати із грою між двома користувачами.
Тому створимо ці три компоненти наступними командами:
ng gc login;
ng gc online;
ng gc 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 у кореневому модулі, інакше ви побачите наступну помилку в консолі браузера:
Може бути '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.
У наступній статті буде розглянуто процес створення серверної частини та взаємодію його з клієнтською.