1

I'm having this issue on handling async calls on the ngOninit(). Basically I need to load a few things sequentially but I don't know how.

Here's the code:

    import { Component, OnInit } from '@angular/core';
    import { Match, Game, participantIdentities, player } from '../../models/match';
    import { MatchService } from '../../services/match.service';
    import { Global } from '../../services/global';
    import { Observable } from 'rxjs';

    @Component({
      selector: 'app-players',
      templateUrl: './players.component.html',
      styleUrls: ['./players.component.css'],
      providers: [MatchService]
    })
    export class PlayersComponent implements OnInit {
      public matchs: Array<Match>;
      public games: Array<Game>
      public player: Array<player>;
      public players: []
      constructor(
        private _matchService: MatchService
      ) { 
        this.games = []
        this.player = []
      }

      ngOnInit(): void {
        this.getMatchs()
      }

      getMatchs(){
          this._matchService.getMatchs().subscribe(
            response =>{
              
              this.matchs = response.matchs;
              console.log(this.matchs);
              for(let i = 0; i<this.matchs.length; i++){
                this.games[i] = this.matchs[i].game;
              };
              console.log(this.games)
            }
            ,error=>{
              console.log(<any>error)
            }
          );
        }
      getUsers(){
          let accountId = [];
          for(let i = 0; i < this.matchs.length; i++){
            for(let x = 0; x < this.matchs[i].game.participantIdentities.length; x++){
              if ( typeof(accountId.find(element=>element == this.matchs[i].game.participantIdentities[x].player.summonerName)) === "undefined"){
                accountId.push(this.matchs[i].game.participantIdentities[x].player.summonerName)
                this.player.push(this.matchs[i].game.participantIdentities[x].player)
              }
            } 
          }
          console.log(this.player)
        }
    }

So as you can see, the data that getUsers() function is using, is coming from getMatchs(). if I execute both functions on ngOninit getUsers() it will throw an error, because the other one has not been done. Which makes sense. I could, of course, incorporate getUsers() into a button, but is not really my intention. I had this issue before and I would love resolve it properly. So the question is, how do I wait for it's completion in order to run the next function.

3 Answers 3

1

One way is to place getUsers() right after this.matchs = response.matchs;, then every time you call getMatchs it calls getUsers too.

another way is to move subscription to ngOnInit.


  ngOnInit(): void {
    this.getMatchs().subscribe(response => {
      this.matchs = response.matchs;
      for(let i = 0; i<this.matchs.length; i++){
        this.games[i] = this.matchs[i].game;
      };
      this.getUsers();
    });
  }

getMatchs(){
      return this._matchService.getMatchs();
    }

  getUsers(){
      let accountId = [];
      for(let i = 0; i < this.matchs.length; i++){
        for(let x = 0; x < this.matchs[i].game.participantIdentities.length; x++){
          if ( typeof(accountId.find(element=>element == this.matchs[i].game.participantIdentities[x].player.summonerName)) === "undefined"){
            accountId.push(this.matchs[i].game.participantIdentities[x].player.summonerName)
            this.player.push(this.matchs[i].game.participantIdentities[x].player)
          }
        } 
      }
      console.log(this.player)
    }

Sign up to request clarification or add additional context in comments.

1 Comment

Glad to hear, it isn't an ideal solution, just a simple example to show a possible way to solve it. Feel free to let me know if I can help more here.
1

@satanTime's solution is fine.
However, I'd like to offer another solution in case you don't want to missplace the getUsers() call.

You can try using Promises.

getMatchs(): Promise<void> {
    return new Promise((resolve, reject) => {
        this._matchService.getMatchs().subscribe(
            response => {
                // Do whatever you need
                resolve();
            },
            error => {
                // Handle error
                // reject(); if you want to scale the exception one level upwards.
            }
        );
    });
}

Then, rewrite your ngOnInit method like so

ngOnInit(): void {
    this.getMatchs().then(() => {
        this.getUsers();
    });
}

Which becomes quite a bit more readable.

Get matchs. Once you are done, get the users.

Just for completion and fanciness, you can cast the Observable returned on _matchService.getMatchs() to a Promise, work on it, then return it.

getMatchs = (): Promise<void> => 
    this._matchService.getMatchs().toPromise()
        .then(response => {
            // Do whatever you need
        })
        .catch(err => {
            // Handle error
        });

Hope it helps.

1 Comment

Yes it helps, i was searching on how to use promises correctly with not much success, i ended up making something similar of what @satanTime's propose. I still have a lot of work to do, so any info is welcome. Thx a lot.
1

as you can see there are a few different solutions to your problem. My advice is to keep using the observable chain and wait until you getMatchs finishes. For example:

ngOnInit(): void {
  this.getMatchs().subscribe(res => this.getUsers());
}

then you would have to change your getmatchs functions like this:

getMatchs() {
  this._matchService.getMatchs().pipe(
    tap(result => {
      this.matchs = response.matchs;
      this.games = this.matchs.map(m => m.game);
    })
  );  

By doing this you keep using your observable stream.

That should work but there are other issues that you should be aware of. One of them is that it is a very good practice to unsubscribe from every subscription you want to avoid memory leaks in your app. You can do this by calling unsubscribe() manually or rely on the async pipe from angular.

https://rxjs-dev.firebaseapp.com/guide/subscription

https://angular.io/api/common/AsyncPipe

On the other hand you would get better performance if you could modify your code to reduce the amount of loops that you do over the same info. Check out this other version of GetMatchs I was doing for you (not tested) but hopefully can give you an idea of how to improve the performance of your component:

processMatches() {
   this._matchService.getMatches().pipe(
      tap(response => {
         this.matches = response.matchs;
         let accountId = {};            

         this.matches.forEach((m, index) => {
            this.game[index] = m[index].game;
            this.game[index].participantIdentities
                .forEach(p => {
                    if (!accountId[p.player.sumonerName]) {
                        accountId[p.player.sumonerName] = p.player.sumonerName;
                        this.player.push(p.player);
                    }
                });      
             });

         })
     )
 }

Again, this code is not tested by the idea here is to reduce the loops and also converted the accountId array to an object in order to check for duplicates easier and faster

Happy coding!

1 Comment

Very helpfull information, i didn't think of using .map (i don't completly understand how it works yet). I have this issue in a few instances of my program, so the more i understand it the better. Thx a lot.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.