Jacob CM
Jacob CM

Reputation: 51

Wait for array to finish being populated before being called in angular

This is my code, I start with an empty array and want to call a service that does some work with the populated array. I populate the array using two calls to services which call an API to receive elements to insert into the array.

I can't figure out how to have the last service call wait until the array has been populated before excuting. I think the fact that I have two seperate service calls to wait for, and the fact that they both have forEach loops makes this harder than it could have been. Any ideas?

const subscribers: ISubscriber[] = [];

this.selectedChildren.forEach(child => {
    this.serviceA.getSubscribers(child.id).subscribe( (subs: ISubscriber[]) => {
        subs.forEach(s => {
            subscribers.push(s);
        });
    });
});

this.selectedSubscribers.forEach(sub => {
    this.serviceB.getSubscriber(sub.subscriberId).subscribe( (sub: ISubscriber) => {
        subscribers.push(sub);
    });
});

// subscribers is always empty when this call is made 
// since above code hasn't finished executing
this.serviceC.processSubscribers(subscribers).subscribe( sub => {
    this.toastr.success('Success!');
});

async/await attempt that doesn't work:

async doSomething(){
    const subscribers: ISubscriber[] = [];

    await this.selectedChildren.forEach(async child => {
        await this.serviceA.getSubscribers(child.id).subscribe( (subs: ISubscriber[]) => {
            subs.forEach(s => {
                subscribers.push(s);
            });
        });
    });

    await this.selectedSubscribers.forEach(async sub => {
        await this.serviceB.getSubscriber(sub.subscriberId).subscribe( (sub: ISubscriber) => {
            subscribers.push(sub);
        });
    });

    // subscribers is always empty when this call is made 
    // since above code hasn't finished executing
    this.serviceC.processSubscribers(this.id, subscribers).subscribe( id => {
        this.toastr.success('Success!');
    });
}

Promise.all attempt that doesn't work:

doSomething(){
    const subscribers: ISubscriber[] = [];
    const promises = [];

    this.selectedChildren.forEach(child => {
        promises.push(this.serviceA.getSubscribers(child.id).subscribe( (subs: ISubscriber[]) => {
            subs.forEach(s => {
                subscribers.push(s);
            });
        }));
    });

    this.selectedSubscribers.forEach(sub => {
        promises.push(this.serviceB.getSubscriber(sub.subscriberId).subscribe( (sub: ISubscriber) => {
            subscribers.push(sub);
        }));
    });

    // subscribers is always empty when this call is made 
    // since above code hasn't finished executing
    Promise.all(promises).then( a => {
        this.serviceC.processSubscribers(this.id, subscribers).subscribe( id => {
            this.toastr.success('Success!');
        });
    });
}

Upvotes: 2

Views: 2667

Answers (2)

Jacob CM
Jacob CM

Reputation: 51

This is the solution I found that ended up working using promises:

doSomething(){
    const subscribers: ISubscriber[] = [];
    const promises = [];

    this.selectedChildren.forEach(child => {
        promises.push(this.serviceA.getSubscribers(child.id).toPromise().then( (subs: ISubscriber[]) => {
            subs.forEach(s => {
                subscribers.push(s);
            });
        }));
    });

    this.selectedSubscribers.forEach(sub => {
        promises.push(this.serviceB.getSubscriber(sub.subscriberId).toPromise().then( (sub: ISubscriber) => {
            subscribers.push(sub);
        }));
    });

    // subscribers is always empty when this call is made 
    // since above code hasn't finished executing
    Promise.all(promises).then( a => {
        this.serviceC.processSubscribers(this.id, subscribers).subscribe( id => {
            this.toastr.success('Success!');
        });
    });
}

Upvotes: 0

Benjamin Caure
Benjamin Caure

Reputation: 2403

Solution using rxjs operators :

  • remember you should have only 1 subscribe in observable chain
  • combineLatest is equivalent to Promise.all
  • switchmap allows you to change the results type of serviceA and serviceB to serviceC
    const subscribers: ISubscriber[] = [];
    const subscriberObservables = [];

    // First you add all async calls into an array
    this.selectedChildren.forEach(child => {
        subscriberObservables.push(this.serviceA.getSubscribers(child.id));
    });

    this.selectedSubscribers.forEach(sub => {
        subscriberObservables.push(this.serviceB.getSubscriber(sub.subscriberId));
    });

    // then you run all of them in parallel
    combineLatest(subscriberObservables)
      .pipe(
        map((arrayOfArrays:ISubscriber[][]) => arrayOfArrays.flat()),
        switchMap( (subs: ISubscriber[]) => this.serviceC.processSubscribers(subscribers))
      )
      .subscribe( sub => {
          this.toastr.success('Success!');
      });

Upvotes: 3

Related Questions