angulartypescriptangular-in-memory-web-api

Instance variable not assigned in Observable<> subscribe?


My games array is not being assigned when subscribing from Obersavable for some reason even though I for sure get the correct data (array) back from the server.

Here is my game.service.ts:

import { Injectable } from '@angular/core';
import { Game } from '../models/game';
import { of, Observable } from 'rxjs';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { catchError, map, tap } from 'rxjs/operators';
import { NGXLogger } from 'ngx-logger';

@Injectable({
  providedIn: 'root'
})
export class GameService {
  private gamesUrl: string = 'api/games';

  constructor(private http: HttpClient, private logger: NGXLogger) { }

  getGames(): Observable<Game[]> {
    return this.http.get<Game[]>(this.gamesUrl);
  }

  getGame(id: number): Observable<Game> {
    const url: string = (`${this.gamesUrl}/${id}`);
    return this.http.get<Game>(url).pipe(tap(_ => this.logger.debug(`fetched game id=${id}`)), catchError(this.handleError<Game>(`getGame id=${id}`)));
  }

  log (operation: string) {
    console.log(operation);
  }

    /**
   * Handle Http operation that failed.
   * Let the app continue.
   * @param operation - name of the operation that failed
   * @param result - optional value to return as the observable result
   */
  private handleError<T> (operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {

      console.error(error); // log to console instead
      this.logger.debug(`${operation} failed: ${error.message}`);
      return of(result as T);
    };
  }
}

And here is my games.component.ts:

import { Component, OnInit } from '@angular/core';
import { Game } from '../../models/game';
import { GameService } from 'src/app/services/game.service';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-games',
  templateUrl: './games.component.html',
  styleUrls: ['./games.component.css']
})
export class GamesComponent implements OnInit {
  games: Game[];

  constructor(private gameService: GameService) { 
  }

  ngOnInit() {
    this.getGames();
  }

  getGames(): void {
    this.gameService.getGames().subscribe(games => this.games = games);
    console.log(`games: ${this.games}`);
  }

  getGame(id: number): Observable<Game> {
    return this.gameService.getGame(id);
  }
}

As you can see, I am making a call to getGames in games.component.ts in which the game.service.ts is returning the response (Observable) back.

For some reason subscribe(games => this.games = games) does not work and I get 'undefined' for the games instance variable. I for sure am getting the correct response as subscribe(games => console.log(games)) does not show 'undefined' but an array of Objects instead.

Why isn't my instance variable being assigned?

Edit: Here is the output in console.log if I do subscribe(games => console.log(games))

enter image description here

Edit: If I do the following the console log is correct. But if I reference the 'games' array outside of it, I get undefined again:

  getGames(): void {
    this.gameService.getGames().subscribe((games) => {
      this.games = games;
      console.log(`games: ${this.games}`);
    });
    console.log(`all games: ${this.games}`); //this is undefined
  }

EDIT: SOLVED - THANK YOU dileepkumar jami The solution was to remove the $ symbol and '| async' in my template:

<li *ngFor="let game of games$ | async">{{game.title}}</li>

to

<li *ngFor="let game of games">{{game.title}}</li>

Solution

  •   1.    getGames(): void { 
      2.      this.gameService.getGames().subscribe((games) => {
      3.        this.games = games;
      4.        console.log(`games: ${this.games}`);
      5.      });
      6.      console.log(`all games: ${this.games}`); //this is undefined
      7. }
    

    I numbered your lines of code.

    As expected, the line6 will return undefined.

    The code from line 2 to line 5 needs some time to be finished because it has an API call.

    Because Javascript is asynchronous, it will not wait for the code (from line 2 to 5) to be finished. It starts executing the line6. But, by that time, this.games is undefined.

    Even when you see in the browser console, you will see the output of the line6 first and then you can see the line4

    You can execute the below block to see how javascript works asynchronously

    function myFunction() {
      console.log('Hello');
    }
    
    setTimeout(myFunction, 3000);
    console.log('hi');
    As you can see, even though console.log('hi'); was written after setTimeout(myFunction, 3000);, hi would be printed first and then hello.
    
    <p> So, javascript did not wait for the setTimeout(myFunction, 3000); to be finished and it started to execute the next line
    </p>

    EDIT: SOLVED - THANK YOU dileepkumar jami The solution was to remove the $ symbol and '| async' in my template:

    <li *ngFor="let game of games$ | async">{{game.title}}</li>
    

    to

    <li *ngFor="let game of games">{{game.title}}</li>