Pausing until all RxJS calls are completed before moving on to the next block of code

I have a scenario where I am iterating over a list of streets and making API calls for each street. I am storing the responses in an Object Array and want to execute the next code block only after all the requests are finished. However, I am facing an issue where the Object Array appears to be empty when trying to use it in the next code block. Here is the relevant code snippet:

export class MyComponent{
  addressInfoArray: AddressInfo[] = [];

  // some code ...

  prepareStreetInformations(): void {
    // some code ....

    this.fillArray(streets, url);
    this.doSomethingWithArray(this.addressInfoArray); // <--- length = 0 and doesn't wait for finishing the fillArray() method
  }
}

fillArray(streets: Street[], url: string): void { // streets has a length of 150
  for (const street of streets) {
    this.http.get<AddressInfo>(`${url}/street.name`).subscribe(response => {
      this.addressInfoArray.push(response);
    });
  }
}

My question is: How can I make the doSomethingWithArray() method wait for the fillArray() method to completely finish, and why does it not recognize that the Object Array is already filled?

Answer №1

Utilizing RxJS does not involve forcing a function call to "wait" for a previous function to complete. Instead, you can create an observable that emits the necessary data based on other observables.

In this scenario, it appears that you require an observable that emits an array of AddressInfo.

We can establish a getStreets() method that yields Observable<Street[]> and a getAddressInfo() method that accepts a street parameter and returns Observable<AddressInfo>.

Subsequently, you can generate an observable that will emit your AddressInfo[], utilizing switchMap and forkJoin:

1  export class MyComponent {
2  
3    getStreets(): Observable<Street[]> {
4      this.http.get<Street[]>(...);
5    }
6  
7    getAddressInfo(street: Street): Observable<AddressInfo> {
8      this.http.get<AddressInfo>(`${this.url}/${street.name}`); // avoid subscribing here
9    }
10
11   addressInfos$: Observable<AddressInfo[]> = this.getStreets().pipe(
12     switchMap(streets => forkJoin(
13       streets.map(s => this.getAddressInfo(s))
14     ))
15   );
16 
17 }

The forkJoin function creates a single observable that emits an array consisting of the results from all its input observables. Hence, the function is provided with an array of observables as inputs.

Given that you possess an array of streets. On line #13, we straightforwardly map it to an array of observables that obtain the address information. When the forkJoined observable is subscribed to, it emits an array of AddressInfo (the outcomes of all individual http calls)

To manage the subscription to this "forkjoined observable", we utilize switchMap. Consequently, we obtain a singular addressInfos$ observable that emits the AddressInfo[].

Note that we have not subscribed yet. To conduct your tasks, you can simply subscribe as follows:

addressInfos$.subscibe(
  infos => doSomethingWithArray(infos)
);

Nevertheless, in Angular, a common approach involves further transforming the emitted data into the format required by your template:

templateData$ = this.addressInfos$.pipe(
  map(infos => {
    // carry out operations on the array here :-)
  })
);

Subsequently, within your template, you can leverage the async pipe:

<div *ngIf="templateData$ | async as data">
  <ul>
    <li *ngFor="item of data">{{ item }}</li>
  </ul>
</div>

Answer №2

Consider utilizing forkJoin method for optimal performance;

let streetObservers = this.streets.map(street => this.http.get<AddressInfo>(`${url}/street.name`));
forkJoin(streetObservers).subscribe((responseData: any) => {
  this.addressInfoArray = responseData; // an array containing address information
  this.processArray(this.addressInfoArray); // <--- the array length is 0 and doesn't wait for the fillArray() method to complete

});

Answer №3

It seems like there may be a misunderstanding of how Observables function. In the provided code snippet, the fillArray function simply dispatches the http calls and then the function ends. As a result, when the doSomethingWithArray function is called next in the code, the addressInfoArray is not yet populated. It is only after some time, when a http call completes and the callback function in the subscribe function is executed, that the addressInfoArray.push is called. This behavior is consistent when using subscribe, as it executes the provided function once the Observable is complete.

If you need to wait for multiple Observables to complete, you can utilize combineLatest. This method creates an Observable that emits an array containing all the values from the original Observables. Here is an example of how you can implement this:

prepareStreetInformations(): void {
  this.fillArray(streets, url).subscribe(addressInfoArray => {
    this.doSomethingWithArray(this.addressInfoArray)
  });
}
  
fillArray(streets: Street[], url: string): Observable<AddressInfo[]> {
  return combineLatest(
    streets.map(street => this.http.get<AddressInfo>(`url/${street.name}`))
  );
}

It is important to note that the fillArray function now returns an Observable that you can subscribe to, and it no longer modifies any class properties directly. If you require access to a class property, you will need to define it within the callback function passed to the subscribe method.

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

Encountering a "Missing Access" error on the Discord.js API when trying to register my slash commands

Three years ago, I created a small Discord bot in Typescript that is now present on over 80 guilds. Recently, I made the decision to update it from discord.js-v12.3.1-dev to discord.js-v13.6, while also integrating the popular slash commands feature. Howe ...

The issue of sending a POST request with form-data body parameters is not functioning as expected in Angular 2

I have successfully sent a file and other parameters in the body with type 'form-data' using Postman. Now, I am trying to achieve the same functionality in Angular 2. Please refer to the screenshot of the request made in Postman: https://i.stack. ...

Encountering an Angular 9 Ivy issue when using the <mat-form-field> with multiple mat-hints

After migrating to angular9 Ivy, I encountered an issue with multiple mat-hints in a single component. Before the update, my code looked like this: <div class="example-container"> <mat-form-field hintLabel="Max 10 characters" ...

TS1316 Error: You can only have global module exports at the top level of the file

Encountering difficulties while trying to compile an older typescript project that I am revisiting. The build process is failing due to an issue with q. I suspect it may be related to the tsc version, but no matter which version I try, errors persist. Som ...

A generic function in Typescript that can accept an object with the first argument as a specified key

I am looking to create a function that will take a string as its first argument and will only accept a second argument of type object if it contains the first argument as a key with a boolean value: const checkFlag = (str:string, obj) => obj[str] Alth ...

The object literal can only define properties that are already known, and 'data' is not found in the type 'PromiseLike<T>'

When making a request to a server with my method, the data returned can vary in shape based on the URL. Previously, I would cast the expected interface into the returned object like this: const data = Promise.resolve(makeSignedRequest(requestParamete ...

Deactivate the selection option in Syncfusion NumericTextbox

I have integrated an Angular NumericTextbox component from Syncfusion into my application. A problem we encountered is that when the input is clicked, it automatically gets selected. Is there a way to disable this behavior? Problem: https://gyazo.com/a72b ...

Tips for updating the date separator in Angular 2

When using the date pipe to format a date, I am struggling to change the date separator. My goal is to format the date as "27.07.2016". Despite trying the code below: {{dateValue | date:'dd.MM.yyyy'}} The output still displays the date as "27/0 ...

Functions designed to facilitate communication between systems

There is an interface that is heavily used in the project and there is some recurring logic within it. I feel like the current solution is not efficient and I am looking for a way to encapsulate this logic. Current code: interface Person { status: Sta ...

Issue with CSS files in Jest"errors"

I'm currently facing an issue while trying to pass my initial Jest Test in React with Typescript. The error message I am encountering is as follows: ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){.App ...

Error message: Trying to use the data type 'String' as an index in the React dynamic component name map won't work

I have successfully implemented the code below and now I am attempting to convert it to Typescript. However, even though I can grasp the error to some extent, I am unsure of how to correct it. At present, I am allowing a component to have a prop called "i ...

Vue.js with TypeScript: The property 'xxx' is not found on the type 'never'

I have a computed method that I am trying to execute: get dronesFiltered(){ const filtered = this.drones.filter((drone) => { return drone.id.toString().indexOf(this.filterId) > -1 && drone.name.toLowerCase().toString().in ...

Looking for Angular 2 material components for dart with CSS styling? Need help centering a glyph on your page?

HTML: <div class="border"> <glyph class="center" [icon]="'star'" ></glyph> <div class="centerText"> This Is Text that is centered. </div> </div> Css: .centerText{ text-align: center ...

An issue with event listeners in Vue 3 and Pixi.js arises when working with DisplayObjects that are either created as properties of classes or inherited from parent classes

Utilizing PIXI js in conjunction with Vue 3 (see code snippet below) Due to the consistent pattern of most graphics with varying behaviors and properties, we opted for an OOP approach with TypeScript to prevent code duplication. However, following this app ...

Creating a table with merged (colspan or rowspan) cells in HTML

Looking for assistance in creating an HTML table with a specific structure. Any help is appreciated! Thank you! https://i.stack.imgur.com/GVfhs.png Edit : [[Added the headers to table]].We need to develop this table within an Angular 9 application using T ...

Attempting to utilize Array Methods with an Array Union Type

Currently, I am in the process of refactoring an Angular application to enable strict typing. One issue I have encountered is using array methods with an array union type in our LookupService. When attempting to call const lookup = lookupConfig.find(l =&g ...

How to conceal parameters in Angular URL?

Within the modeling-agency component, there is a button that routes to editModelingAgency/{{element.userid}}. Currently, clicking on this button takes the user to a specific route with a link like /base/editModelingAgency/15, but I want to show the link as ...

What's the best way to assign a dual-value condition within a form group field?

// Setting up a form group in Angular this.form = this.fb.group({ id:[], name: [ details.name || '' ] }) I am wondering if it is possible to assign a value in the form based on the content of details.name. If details.name has ...

Navigating through different routes in an Angular application can be tricky, especially when dealing

I am facing an issue with the navigation links in my sidebar, which are located within a child module named "login". When I click on "Classroom", it correctly directs me to "login/classroom". However, when I click on "Assignments", it appends "assignment ...

How can I display an array in reverse order using *ngFor in a template?

Just starting out with Angular and I'm looking to display an array in reverse order. Here's what I have: <ng-container *ngFor="let user of _users.reverse(); let i = index"> <tr> <td>{{ _users[i].firstN ...