Transitioning from Angular's promises to Observables (RxJS) for making repetitive API calls to a single endpoint

I am facing an issue while working with Angular and I am seeking a solution using Observables instead of Promises (async/await) which I am currently using.

The API endpoint allows sorting and pagination by passing parameters like pageSize and page to fetch the data. If no parameters are sent, it responds with a maximum of 1000 items. However, the total number of items can range from tens of thousands to just a few. The response from the API always follows this format:

{
  items: [...], // (array of objects)
  totalNumberOfItems: 123 // (total number of database entries)
}

Currently, my code looks something like this (without try-catches and unnecessary details), using Promises:

async getAllItems(): Promise<any[]> {
  let page: number = 1;
  let pageSize: number;
  let items: any[] = [];
  let totalNumberOfItems: number;

  let initialResponse = await this.someService.getItems().toPromise();
  items = [...items, ...initialResponse.items];
  
  ...

  return items;
}

I am struggling to handle the Observable properly starting from the initial API call. I am considering something like this:

interface IResponse {
  items: any[];
  totalNumberOfItems: number;
}

getAllItems(): Observable<IResponse> {
  let page: number = 1;
  let pageSize: number;
  let totalNumberOfItems: number;

  return this.someService.getItems().pipe(
    tap(res => {
      ...
    })
    // TODO: Need to figure out how to make subsequent calls iteratively and combine them with initial response
  );
}

Edit: Following the suggested approach by thisdotutkarsh, I have made some modifications to my code and it seems to be working as expected:

getAllItems(): Observable<ItemsResponse> {
  let page: number = 1;
  let pageSize: number;
  let totalNumberOfItems: number;

  return this.service.getItems().pipe(
    tap(response => {
      ...
    }),
    expand(response => {
      ...
    }),
    reduce((acc, response) => {
      ...
    })
  );
}

Answer №1

To achieve the desired functionality, utilize the RxJS operators expand and reduce.

The expand operator recursively maps each value from the source to an Observable, which is then merged into the output Observable. On the other hand, the reduce operator applies an accumulator function across all values emitted by the source Observable and returns a single accumulated result upon completion.

getAllItems() {
let page: number = 1;
let pageSize: number;
let totalNumberOfItems: number;

return this.service.getItems().pipe(
  tap(response => {
    totalNumberOfItems = response.totalNumberOfItems; /* The tap operator performs side-effect once per subscribe */
  }),
  expand((response) => {
    pageSize += response.items.length ? response.items.length : 0;

    return pageSize <= totalNumberOfItems ? this.service.getItems(page++, pageSize) : Observable.empty();
  }),
  reduce((acc, response) => {
    return acc.concat(response.items);
  }, [])
  .catch(error => console.log(error))
  .subscribe((iResponse)) => {
    ...
  });
});
}

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

Distinguish between two Angular components originating from a common parent component

There is a modal component that displays a message along with an accept button. The function triggered by the accept button depends on the caller. This versatile component can generate multiple modals at different times. An issue arises when opening the ...

Angular Material Datatables - Issue with Paginating Data from Firestore

Data has been retrieved from Firestore and transformed into an Observable array with the InvoiceItem type. The data loads correctly onto the datatable, but there seems to be an issue initializing the paginator with the array's length. This could poss ...

Tips for Ensuring Type Safety in Angular Kendo-Grid

Could the type of the dataItem variable not be inferred from the binding to [kendoGridBinding], which is of type Array<T>? Do I need to use a type guard or assertion for each column? <kendo-grid [kendoGridBinding]="processTable()"> ...

Node Express and Typescript app are failing to execute new endpoints

Hello, I'm currently diving into Node express and working on a simple CRUD app for pets. So far, I've successfully created 2 endpoints that are functioning properly. However, whenever I try to add a new endpoint, Postman fails to recognize it, g ...

An error occurred as the requested resource does not have the necessary 'Access-Control-Allow-Origin' header. The response code received was 403

I am working with Angular products services that make calls to the "http://jsonplaceholder.typicode.com/posts" URL using the HttpClient method. However, I am encountering the error message "No 'Access-Control-Allow-Origin' header is present on t ...

What is the correct way to properly import a non-relative path in TypeScript?

Struggling with importing into my TypeScript class. // Issue arises here... import * as Foundation from 'foundation/foundation'; // Everything runs smoothly until the above import is added... export class HelloWorldScene { constructor() { ...

When utilizing Next.js with TypeScript, you may encounter an error message stating that the property 'className' is not found on the type 'IntrinsicAttributes & IntrinsicClassAttributes'

I recently put together a basic nextjs app using typescript. Here's the link to the example: https://github.com/zeit/next.js/tree/master/examples/with-typescript However, I encountered an issue where I am unable to add the className attribute to any ...

Troubleshooting error handling in Angular 2 and Web API when using Chrome versus Internet Explorer for HTTP PUT requests

Currently, I am working on integrating Angular 2 with Web API in a test project to enhance my skills. However, I have encountered an issue while using the http.put method and would greatly appreciate any insights on this. So far, I have successfully manag ...

Changing Observable to Promise in Angular 2

Q) What is the best way to convert an observable into a promise for easy handling with .then(...)? The code snippet below showcases my current method that I am looking to transform into a promise: this._APIService.getAssetTypes().subscribe( assetty ...

Optimal Approach for Redirecting Authorization

I'm currently working on setting up an authorization feature for my Angular application. Here is the detailed process I am following: First, I generate a state and code in the front end. Upon clicking the login button, the application redirects to /a ...

Encountering an error where `ng serve` is failing because it cannot locate the local workspace file (`angular.json`) for angular 5

Ensuring I'm in the correct directory. After successfully running `ng serve` earlier today, I am now encountering errors: Local workspace file ('angular.json') could not be found. Error: Local workspace file ('angular.json') coul ...

Adjust the background color of the default Angular material theme

I'm currently working with the pre-made purple-green angular material theme using .css. I want to switch to a white background, but I'm having trouble changing it from dark grey. I've attempted removing the class "mat-app-background" from th ...

The TypeScript error reads: "An element is implicitly assigned the 'any' type because an expression of type 'any' cannot be used to index a specific type."

[Hey there!][1] Encountering this TypeScript error message: { "Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{ 0: { image: string; title: string; text: string; }; 1: { ...

The specified type '{ children: Element; ref: MutableRefObject<HTMLDivElement>; }' cannot be matched with type 'IntrinsicAttributes & RefAttributes<HTMLDivElement>' in the assignment

Encountering an issue with fowardRef in ReactJS where everything seems fine, but an error pops up: Type '{ children: Element; ref: MutableRefObject<HTMLDivElement>; }' is not assignable to type 'IntrinsicAttributes & SectionContent ...

Tips for assigning an ID to a delete button when utilizing setValue in Angular 6

When we use setValue, how can we assign the ID of a row to the delete button? for (let i = 0; i < this.education.length; i++) { if (i !== 0) { const control = <FormArray>this.editEducation.controls['educationArray']; ...

Is it possible to duplicate a response before making changes to its contents?

Imagine we are developing a response interceptor for an Angular 4 application using the HttpClient: export class MyInterceptor implements HttpInterceptor { public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<an ...

Struggling to set a theme for Angular Ag Grid within an Angular 10 project

Currently, I am working on a project where Angular 10 is being used along with ag-grid-community version 25.1. When running the application with ag-theme-alphine.css, I encountered the following error: Error: Failed to locate '../node_modules/ag-grid- ...

Finding the width of a div element in Angular 2

I have encountered several posts, but none of them quite achieve what I am trying to do. I am dealing with a table that exceeds the width of the page and, due to certain requirements, I need to measure its width. I attempted: @ViewChild('tableToMea ...

Harness the Power of Generics in TypeScript for Automatic Type Inference

function execute<T>(operation1: (input: T) => void, operation2: (input: { key: string }) => T): void {} execute((params) => {}, () => 23); // The params here can be correctly inferred as number execute((params) => {}, (arg) => 23) ...

There seems to be an issue with the request header field Authorization, as it is not permitted by the Access-Control-Allow-Headers in

My project involves using Angular 2 for the client side and Laravel 5.4 for the server side. I have set up APIs in Laravel and am making requests to them from Angular 2. In Laravel, I have configured Oauth 2 passport service to authenticate all APIs. For ...