Ensure that the array is completely populated before calling it in Angular

My code starts with an empty array and I need to call a service that works with the populated array. Two service calls are used to populate the array, both fetching elements from an API. The issue is ensuring the last service call waits for the array to be fully populated before executing. This task becomes complicated due to having two separate service calls to wait for, each containing forEach loops. Any suggestions on how to solve this?

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!');
});

An attempt using async/await:

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!');
    });
}

Attempting to use Promise.all:

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!');
        });
    });
}

Answer №1

Here is a solution that utilizes rxjs operators:

  • It's important to note that you should only have 1 subscribe in the observable chain
  • combineLatest is similar to Promise.all
  • switchmap enables you to transform the results from serviceA and serviceB into serviceC
    const subscribers: ISubscriber[] = [];
    const subscriberObservables = [];

    // First, all async calls are added to 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, they are executed in parallel
    combineLatest(subscriberObservables)
      .pipe(
        map((arrayOfArrays:ISubscriber[][]) => arrayOfArrays.flat()),
        switchMap( (subs: ISubscriber[]) => this.serviceC.processSubscribers(subscribers))
      )
      .subscribe( sub => {
          this.toastr.success('Success!');
      });

Answer №2

After some experimentation, I managed to come up with a solution utilizing promises:

executeTask(){
    const allSubscribers: ISubscriber[] = [];
    const promiseArray = [];

    this.selectedChildren.forEach(child => {
        promiseArray.push(this.serviceA.getSubscribers(child.id).toPromise().then((subs: ISubscriber[]) => {
            subs.forEach(subscriber => {
                allSubscribers.push(subscriber);
            });
        }));
    });

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

    // 'allSubscribers' remains empty at this point
    // as the code above is still being executed
    Promise.all(promiseArray).then(() => {
        this.serviceC.processSubscribers(this.id, allSubscribers).subscribe(result => {
            this.toastr.success('Success!');
        });
    });
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Issues detected with the functionality of Angular HttpInterceptor in conjunction with forkJoin

I have a Service that retrieves a token using Observable and an HttpInterceptor to inject the token into every http request. It works seamlessly with a single request, but when using forkJoin, no response is received. Here is the code for the interceptor: ...

I am unable to utilize the Web Share API for sharing a file within my React app written in TypeScript

Trying to launch a WebApp for sharing files has been quite a challenge. After some thorough research, I stumbled upon the Web Share API which seemed like the perfect solution based on standard practices. The documentation provided a clear outline of how it ...

An error was encountered when attempting to proxy the request /api/v0/dataservice/cluster/clusters from localhost:4300 to http://localhost:30510. The connection was refused (ECONNREFUSED)

I recently encountered an issue while working on my Angular 6 application. I attempted to fix vulnerabilities by running npm audit fix and npm audit fix --force, but this resulted in my application not compiling or working properly. To resolve this, I ende ...

Enhance your MaterialUI Button with a higher order component that accepts a component

I am currently working on implementing a Higher Order Component (HOC) for the MaterialUI Button component: import {Button as MUIButton, ButtonProps} from '@material-ui/core'; import * as React from 'react'; export const Button = (props ...

What is the solution for resolving array items in a GraphQL query?

I am facing an issue with my graphql Query, specifically in trying to retrieve all the fields of a Post. { getSpaceByName(spaceName: "Anime") { spaceId spaceName spaceAvatarUrl spaceDescription followin ...

Using external URLs with added tracking parameters in Ionic 2

I am looking to create a unique http link to an external URL, extracted from my JSON data, within the detail pages of my app. Currently, I have the inappbrowser plugin installed that functions with a static URL directing to apple.com. However, I would lik ...

Difficulty Encountered While Deploying Mean Stack Application on Heroku

I am embarking on my first journey of building a MEAN stack application, and I successfully created it locally. However, when attempting to host it on Heroku, things didn't go as planned. After researching online, I learned that both Angular and Expre ...

Encountered a bun runtime error stating "Possibly require an `extends React.JSX.IntrinsicAttributes` constraint for this type parameter."

I have a good understanding of ReactJS, but this topic seems to be more advanced. I am working with generics in TypeScript and have the following code: export const withPopover = <T,>(WrappedComponent: React.ComponentType<T>) => { const ...

Mocking a service dependency in Angular using Jest and Spectator during testing of a different

I am currently using: Angular CLI: 10.2.3 Node: 12.22.1 Everything is working fine with the project build and execution. I am now focusing on adding tests using Jest and Spectator. Specifically, I'm attempting to test a basic service where I can mo ...

How can you apply an active class using React-Router?

My React-Router navigation issue nav.tsx import React from 'react' import { menu } from './menu' import { Link } from 'react-router-dom' import styles from './HamburgerMenu.module.scss' const HamburgerMenu: React.F ...

Monitor changes in Angular Reactive forms to retrieve the field name, as well as the previous value and the current value of the field

this.formData = this.fb.group({ field1: '', field2: '' }); this.formData.valueChanges.pipe(startWith(null), pairwise()) .subscribe(([previous, current]: [any, any]) => { console.log('PREVIOUS', previous); co ...

The TypeScript compiler generates a blank JavaScript file within the WebStorm IDE

My introduction to TypeScript was an interesting experience. I decided to convert a simple JavaScript application, consisting of two files, into TypeScript. The first file, accounts.ts, contains the main code, while the second one, fiat.ts, is a support f ...

An issue arises with launching karma.js when importing node-openid-client in a TypeScript specification file

Utilizing the node-openid-client library for OpenIDConnect based authentication with an OpenID Provider. Encountering challenges while attempting to write test cases for the program. The application runs smoothly from node CLI, obtaining the code and toke ...

What is the best way to export Class methods as independent functions in TypeScript that can be dynamically assigned?

As I work on rewriting an old NPM module into TypeScript, I encountered an intriguing challenge. The existing module is structured like this - 1.1 my-module.js export function init(options) { //initialize module } export function doStuff(params) { ...

How to Invoke a TypeScript Function in Angular 2 Using jQuery

Using the Bootstrap-select dropdown in Angular 2 forms with jQuery, I am attempting to call a Typescript method called onDropDownChangeChange on the onchange event. However, it seems to not be functioning as expected. import { Component, OnInit, ViewChi ...

Tips for styling the value of a control in an Angular Reactive Form

What is the best way to format a value in an Angular Form Control? For example, if I have a date field returned from a database with seconds and milliseconds included, how can I format it to show only day, month, year, etc. similar to using the date pipe? ...

Creating a comprehensive object within a single interface using Typescript

Here is an example of an Object in TypeScript: export class test{ recordname: string; comments: [{ comment: string }] } To define it using one interface instead of multiple interfaces, you can try something like this: export int ...

Encountering a PropertyTypeError while attempting to process a payment via Stripe in conjunction with use-shopping-cart on Next.js

Upon reaching the checkout page, I encounter the message: Invalid value with type "undefined" was received for stripe. Valid type for stripe is "string". This issue seems to be related to the redirectToCheckout function. Can someone assist me? The cart-s ...

When transitioning the Next application to Typescript, errors are displayed with JSX, but it functions correctly in the browser

After migrating my Next App from JS to TSX, I noticed that the JSX in my TSX file is showing errors and underlined, even though the app runs fine in the browser. I'm puzzled as to why this inconsistency exists. Can anyone provide assistance in resolvi ...

Assistance is not aligned

I have a service that is utilized for making calls to a webapi. The webapi URL is fetched from a local json file. An issue arises where the service method ends up calling the incorrect webapi URL, and it seems likely that this happens because the methods ...