Search code examples
javascriptangulartypescriptasynchronousngoninit

Problem with calling multiple asynchronous functions in ngOnInit


Good afternoon,

I am struggling with placing functions in ngOnInit. If I understand what is going wrong correctly, the problem appears to be that when I place asynchronous functions in ngOnInit and the next function depends on the complete execution of the first, if the first has not been completed, then the data I require is still undefined and it will fail.

What I want to do is quite simple, really. ngOnInit populates two arrays by querying my API. One is groups associated with a user, and another is all groups (these come from a DB). It will then populate a third array by comparing these two (allgroups - groupsassociated = groupsavailable).

After this initial populating, I want to be able to do further comparisons between the arrays depending on user interaction. These functions work, but about 10% of the time I will receive an undefined problem which can be cleared up by refreshing the page (sometimes two or three times). This suggests to me that it's a problem of order of operations.

How can I move forward here? Can I make these function calls synchronous? Can I chain them together somehow?

I appreciate any help.

Relevant code below:

NGONINIT AND THREE FUNCTIONS:

  ngOnInit(): void {
    this.populatePickListUserAssociatedGroups();
    this.populateOriginalUserAssociatedGroups();
    this.populateAvailableGroups();
  }
  // METHODS
  private populatePickListUserAssociatedGroups = () => {
    this.userDataService.getSingleUserAssociatedGroups()
      .then(groups => this.pickListUserAssociatedGroups = groups)
      .then(groups1 => this.originalUserAssociatedGroups = groups1);
  }
  private populateOriginalUserAssociatedGroups = () => {
    this.userDataService.getSingleUserAssociatedGroups().then(groups => this.originalUserAssociatedGroups);
  }
  private populateAvailableGroups = () => {
    this.userDataService.getAllGroups()
      .then(groups => this.allGroups = groups)
      .then(groups => groups.filter(i1 => !this.originalUserAssociatedGroups
        .some(i2 => i1.id === i2.id)))
      .then(groups => this.availableGroups = groups);
  }

I have described my main problem, but additionally I have another. For some reason the two arrays: pickListUserAssociatedGroups and originalUserAssociatedGroups always change together. This defeats the purpose. The pick list array represents what the user changes (I'm working with ngPrime pick list) and originalUserAssociatedGroups is supposed to be unchanged and represents what is in the DB so I can later make comparisons between the two.

Hopefully this is clear.

Thanks in advance.


Solution

  • You could achieve that by using rxjs. There is multiple solution to your question but I think the best is to use a forkJoin. Here is how you could achieve that :

    ngOnInit() {
        forkJoin(
          this.populatePickListUserAssociatedGroups(),
          this.populateOriginalUserAssociatedGroups(),
        ).subscribe(finalResult => {
          // do your things with finalResult[0] and finalResult[1]
          this.populateAvailableGroups();
        });
      }
    
      populatePickListUserAssociatedGroups(): Observable<any> {
        return this._myService.getPickListUserAssociatedGroups();
      }
    
      populateOriginalUserAssociatedGroups(): Observable<any> {
        return this._myService.getOriginalUserAssociatedGroups();
      }
    
      populateAvailableGroups() {
        // your code
      }
    

    Note that i use observable, not promise. You might need to add a value check in the subscribe to be sure you have the value you need, but the documentation says :

    This operator is best used when you have a group of observables and only care about the final emitted value of each.

    If your html depends on the data your methods retrieve, you'll have to adapt it, by adding a loader for example, to wait for the subscribe to end since it is asynchronous.

    Finally, your service method should return an observable, and you can do it by simply retunring that response :

    myServiceMethod(): Observable<any> {
      return this._http.get<Whatever>(url);
    }
    

    For your other problem, if the changes of the pick list array is made locally in your constructor then you just have to store it in a variable in the forkJoin subscription : this.mypickListArray = finalResult[0] and just work with it.