Невозможно загрузить переменную из службы в Angular 9 - PullRequest
2 голосов
/ 30 мая 2020

Я пытаюсь создать веб-приложение для игры, которая включает запуск и присоединение комнат. Я использую Angular и Socket.io для проекта. У меня есть компоненты с названиями Startpage и Lobby. Стартовая страница содержит текстовые поля для имени игрока и ID комнаты. На нем также есть кнопки «Начать игру» и «Присоединиться к игре». При нажатии на кнопку «Начать игру» запускается функция startThisGame ().

//startpage.component.ts

import { Component, OnInit, Output, NgZone } from '@angular/core';
import { Router } from '@angular/router';
import { SocketioService } from '../socketio.service';

@Component({
  selector: 'app-startpage',
  templateUrl: './startpage.component.html',
  styleUrls: ['./startpage.component.css']
})

export class StartpageComponent implements OnInit {

  constructor( private router: Router, public zone: NgZone, private socketService: SocketioService ) {}

  ngOnInit() {
    this.socketService.setupSocketConnection();
  }
  testRoomId = 'xxxx'

  startThisGame() {
    var playerName = (document.getElementById("nameInput") as HTMLInputElement).value;
    if (playerName) {
      this.testRoomId = this.socketService.startGame(playerName);
      alert("In component: " + this.testRoomId)
      this.zone.run(() => {this.router.navigate(['/lobby']); });
    }
    else{
      alert("Enter a player name!")
    }
  }
}

Код внутри socketService выглядит следующим образом.

import { Injectable } from '@angular/core';
import * as io from 'socket.io-client';
import { environment } from 'src/environments/environment';

@Injectable({
  providedIn: 'root'
})
export class SocketioService {
  socket;
  public roomId: string;
  public playerName: string;
  constructor() {}

  setupSocketConnection() {
    this.socket = io(environment.SOCKET_ENDPOINT)
    alert("Connection set-up")
  }

  startGame(inputPlayerName) {
    this.socket.emit('startGame', {name: inputPlayerName})
    this.playerName = inputPlayerName
    this.socket.on('recieveRoomId', (data: any) => {
      this.roomId = data.code
      alert("In service startGame: " + this.roomId)
    })
    return this.roomId
  }

  getRoomId() {
    alert("In service getRoomId: " + this.roomId)
    return this.roomId;
  }
}

Код сервера создает уникальный идентификатор комнаты и передает его переменной roomId в socketService. Мой первый вопрос начинается здесь. Если вы заметили, я оставил за собой след предупреждающих сообщений. Я ожидаю, что порядок предупреждений будет alert("In service startGame: " + this.roomId), а затем alert("In component: " + this.testRoomId). Однако я получаю сообщения с предупреждениями в обратном порядке, и roomId не определен, хотя сервер генерирует и выдает уникальный roomId. Мне кажется, я не понимаю, почему это происходит.

Во-вторых, вы можете видеть, что функция startThisGame() заставляет socketService сохранять сгенерированные roomId и playerName внутри класса, а затем перенаправляет приложение на компонент лобби. Код лобби выглядит следующим образом:

import { Component, OnInit, OnChanges, AfterContentInit, SimpleChanges, Input } from '@angular/core';
import { SocketioService } from '../socketio.service';

@Component({
  selector: 'app-lobby',
  templateUrl: './lobby.component.html',
  styleUrls: ['./lobby.component.css']
})
export class LobbyComponent implements OnInit, AfterContentInit {
  constructor(private socketService: SocketioService) { }
  @Input()  roomId: string;

  ngOnInit(): void {
    // alert("On init fetched")
    this.socketService.setupSocketConnection()
    this.roomId = this.socketService.getRoomId()
    if (this.roomId) {
      alert("Value obtained in init: " + this.roomId)
    }
    else {
      alert("No value obtained inside init")
    }
    document.getElementById("generated_code").innerHTML = this.roomId
  }
}

Здесь OnInit, функция socketService.getRoomId () возвращает неопределенное значение. Если я снова вернусь, go на стартовую страницу, введу playerName и снова начну новую игру, будет отображаться ранее созданный roomId. Что мне здесь не хватает? Как мне загрузить roomId и отобразить его при перенаправлении в лобби?

Ответы [ 2 ]

2 голосов
/ 30 мая 2020

Чего вам не хватает, так это концепции asynchronous звонков и observable.

Попробуйте:

service.ts

export class SocketioService {
  socket;
  public roomId: string;
  public playerName: string;
  private roomData$ = new BehaviorSubject<string>(null);
  constructor() {}

  setupSocketConnection() {
    this.socket = io(environment.SOCKET_ENDPOINT)
    alert("Connection set-up")
  }

  startGame(inputPlayerName) : Observable<string>{
    this.socket.emit('startGame', {name: inputPlayerName})
    this.playerName = inputPlayerName
    this.socket.on('recieveRoomId', (data: any) => {
      this.roomId = data.code;
      this.roomData$.next(this.roomId); // <=== emitting event when the roomId is set.
      alert("In service startGame: " + this.roomId)
    })
    return this.roomData$.asObservable(); // <== returns observable
  }

  getRoomId(){
    alert("In service getRoomId: " + this.roomId)
    return this.roomData$.asObservable();  // <== returns observable
  }
}

StartpageComponent.ts

  startThisGame() {
    // not sure why you are getting value in this way. I can't comment because 
    // I dont have access to HTML code
    var playerName = (document.getElementById("nameInput") as HTMLInputElement).value;
    if (playerName) {
      // subscribe to the observable and get the values when prepared.
      // make sure to unsubscribe it in "ngOnDestroy" to avoid memory leaks
      this.socketService.startGame(playerName).subscribe(data => {
           this.testRoomId = data;
           alert("In component: " + this.testRoomId)
           // again, not sure why would you need "this.zone.run()",
           // Handle input values as Angular way and you wont need these patches.
           this.zone.run(() => {this.router.navigate(['/lobby']); });
       });

    }
    else{
      alert("Enter a player name!")
    }
  }

In LobbyComponent.ts

// You can also use RouteParam to pass RoomId rather than using Observable as I have done.
ngOnInit(): void {
    this.socketService.setupSocketConnection()
    // make sure to unsubscribe in ngOnDestroy
    this.socketService.getRoomId().subscribe(data => {
         this.roomId =  data;
         document.getElementById("generated_code").innerHTML = this.roomId;
    })    
  }

Я добавил много комментариев, чтобы объяснить вам, почему я сделали эти изменения. Я бы порекомендовал вам прочитать про Observables и Promises в Javascript, чтобы лучше понять async звонки.

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

Удачного обучения :)

1 голос
/ 30 мая 2020

Проблема здесь в том, что каждый сокет является асинхронным.

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

 if (playerName) {
  this.testRoomId = this.socketService.startGame(playerName);
  alert("In component: " + this.testRoomId)
  this.zone.run(() => {this.router.navigate(['/lobby']); });
}

this.socket.on('recieveRoomId', (data: any) => {
      this.roomId = data.code
      alert("In service startGame: " + this.roomId)
    })

Таким образом выдается предупреждение в случае получения нового идентификатора комнаты asny c. Таким образом, код завершает выполнение функции startgame и возвращается к основной функции, которая затем предупреждает предупреждение «В компоненте» и когда сокет получает событие, он предупреждает диалоговое окно.

Решение, которое я думаю Лучше всего работать с Rx Js и помещать roomId в ReplaySubject.

Так что измените службу на

private roomId: ReplaySubject<string>=new ReplaySubject();
        startGame(inputPlayerName) {
            this.socket.emit('startGame', {name: inputPlayerName})
            this.playerName = inputPlayerName
            this.socket.on('recieveRoomId', (data: any) => {
              this.roomId.next(data.code)
              alert("In service startGame: " + this.roomId)
            })
          }

        getRoomId() {
          alert("In service getRoomId: " + this.roomId)
          return this.roomId.asObservable();
        }

И в компоненте

     ngOnInit(): void {
            // alert("On init fetched")
            this.socketService.setupSocketConnection()
            this.socketService.getRoomId().subscribe((roomId)=>{
                 this.roomId=roomId;
                 //More logic here when getting a new roomId
            });
          }

И не забудьте вызвать метод startGame :) Если вы хотите узнать больше о ReplaySubject, посмотрите его потрясающую docs .

...