Reputation: 79
I am trying to understand Observables, Promises and async operation but I'm obviously getting something wrong. What I want is for the ngOnInit to wait until the currentGame gets loaded from the service. I've put several alerts in so I could see the order in which things are happening. I can see that ngOnInit finishes (and thus the page loads) before my call to the external server (using http.get) is finished. The subscribe in my game component is waiting on the service to finish but the page as a whole isn't.
(ps - yeah it looks like I also need to figure out how to get the game defined when the subscription finishes but I figure I'll attack this one problem at a time).
I've included the code for my component and service below. These are the order in which the alert messages are showing:
Basically it feels like my page isn't awaiting the result of subscribing to the service. I've googled a bunch but don't feel like I'm getting anywhere. Can someone help me understand how to make my page wait for the subscribe to finish before moving along? I'd greatly appreciate the help.
Here's my code:
single-game.component.ts
import { Component, Input, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { ActivatedRoute, ResolveEnd } from '@angular/router';
import { Game } from '../../interfaces/games';
import { Question } from 'src/interfaces/questions';
import { Answer } from 'src/interfaces/answers';
import { GamesService } from 'src/services/games.service';
import { JsonPipe } from '@angular/common';
@Component({
selector: 'app-single-game',
templateUrl: './single-game.component.html',
styleUrls: ['./single-game.component.css']
})
export class SingleGameComponent {
@Input() game?: Game;
questions: Question[] = [];
answers: Answer[] = [];
currentGame!: Game;
constructor(
private gamesService: GamesService,
private route: ActivatedRoute,
) {}
async ngOnInit(): Promise<void> {
alert('in ngOnInit');
await this.getCurrentGame();
alert('finishing ngOnInit')
}
async getCurrentGame(): Promise<void> {
alert("in getCurrentGame")
const id = Number(this.route.snapshot.paramMap.get('id'));
alert (`first time in single game component, ${this.currentGame}`)
await this.gamesService.getCurrentGame(id)
.subscribe(currentGame => {
this.currentGame = currentGame
alert(`in the subscribe of singlegame calling the service and currentGame is ${this.currentGame.Name}`)
});
alert (`back in the single game component and the value returned is ${this.currentGame}`)
}
}
games.service.ts
import { Injectable, OnInit, Pipe } from '@angular/core';
import { Observable, of, throwError } from 'rxjs';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import {catchError, map, tap} from 'rxjs/operators'
import { Game } from '../interfaces/games';
import { Question } from 'src/interfaces/questions';
import { Answer } from 'src/interfaces/answers';
import { ErrorHandlingService } from './error-handling.service';
import { JsonPipe } from '@angular/common';
@Injectable({
providedIn: 'root'
})
export class GamesService {
errHandler: ErrorHandlingService;
games: Game[] = [];
constructor(private http: HttpClient) {
this.errHandler = new ErrorHandlingService();
}
private gamesURLBase = 'http://localhost:5000/api/GamePlay';
/* All Games */
getGames(): Observable<Game[]> {
game: GamesService;
var url:string = this.gamesURLBase + '/GetAllGames';
//TODO: See if the error handling really works
return this.http.get<Game[]>(url)
.pipe(
catchError(this.errHandler.handleError<Game[]>('getGames', []))
);
}
getCurrentGame(gameId: number): Observable<Game> {
game: GamesService;
var url:string = this.gamesURLBase + `/GetSingleGameInfo/${gameId}`;
var currentGame: Game |undefined;
alert(`The URL is ${url}`);
return this.http.get<Game>(url)
.pipe(
tap (data => alert(`in tap of http call and I can see ${JSON.stringify(data)} `)),
catchError(this.errHandler.handleError<Game>('getCurrentGame'))
);
}
}
Upvotes: 0
Views: 623
Reputation: 79
By Jove, I think I've got it.... I FINALLY noticed that the service was bringing back a one element array instead of just a single game. I guess that's the difference between a real-life service and not the single element that was used in the mock-data service I was trying to emulate from this tutorial https://angular.io/tutorial/tour-of-heroes/toh-pt4.
Anyway, so in the component, once the service completes I take the 0th element of that array and everything works. I FINALLY get to see the name of the game on my HTML page.
Just in case anyone else runs into similar issues I've posted my files again below. I took out all the alerts but left a marker at the point where my problem was solved by taking the 0th element. Hope it helps someone in the future.
single-game.component.ts
import { Component, Input, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { ActivatedRoute, ResolveEnd } from '@angular/router';
import { Game } from '../../interfaces/games';
import { Question } from 'src/interfaces/questions';
import { Answer } from 'src/interfaces/answers';
import { GamesService } from 'src/services/games.service';
import { JsonPipe } from '@angular/common';
@Component({
selector: 'app-single-game',
templateUrl: './single-game.component.html',
styleUrls: ['./single-game.component.css']
})
export class SingleGameComponent {
@Input() game?: Game;
questions: Question[] = [];
answers: Answer[] = [];
currentGame!: Game;
constructor(
private gamesService: GamesService,
private route: ActivatedRoute,
) {}
ngOnInit(): void {
this.getCurrentGame();
}
getCurrentGame(): void {
const id = Number(this.route.snapshot.paramMap.get('id'));
this.gamesService.getCurrentGame(id)
.subscribe(currentGame => {
this.currentGame = currentGame[0] // <-- this is what made the difference
});
}
}
game.service.ts
import { Injectable, OnInit, Pipe } from '@angular/core';
import { Observable, of, throwError } from 'rxjs';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import {catchError, map, tap} from 'rxjs/operators'
import { Game } from '../interfaces/games';
import { Question } from 'src/interfaces/questions';
import { Answer } from 'src/interfaces/answers';
import { ErrorHandlingService } from './error-handling.service';
import { JsonPipe } from '@angular/common';
@Injectable({
providedIn: 'root'
})
export class GamesService {
errHandler: ErrorHandlingService;
// games: Game[] = [];
constructor(private http: HttpClient) {
this.errHandler = new ErrorHandlingService();
}
private gamesURLBase = 'http://localhost:5000/api/GamePlay';
/* All Games */
getGames(): Observable<Game[]> {
var url:string = this.gamesURLBase + '/GetAllGames';
//TODO: Come back and see if the error handling really works
return this.http.get<Game[]>(url)
.pipe(
catchError(this.errHandler.handleError<Game[]>('getGames', []))
);
}
getCurrentGame(gameId: number): Observable<Game[]> {
var url:string = this.gamesURLBase + `/GetSingleGameInfo/${gameId}`;
// DEBUGGING NOTE: this is coming back as a single element array of games, not a single game
return this.http.get<Game[]>(url)
.pipe(
// tap (data => alert(`in tap of http call and I can see ${JSON.stringify(data)} `)),
catchError(this.errHandler.handleError<Game[]>('getCurrentGame'))
);
}
}
Upvotes: 0