Angular and RxJS work together to repeat actions even when an HTTP request is only partially successful

I am currently attempting to achieve the following:

  1. Initiate a stop request using a stored ID in the URL.
  2. Send a request with a new ID in the URL and save this new ID.
  3. If the response code is 200, proceed as normal. If it is 204, repeat steps 1 and 2 with a new ID.

Ideally, I would like to implement this without using multiple subscribe() calls, but rather utilize RxJS's pipe for most of the operations.

The existing code structure resembles something like this:
public doStuff(): Observable<string> {
  if (this.id) {
    return this.httpClient.get(`${STOP_URL}/${this.id}`).pipe(
      tap(() => {
        this.id = Date.now();
      }),
      mergeMap(() => this.httpClient.get(`${START_URL}/${this.id}`))
    );
  } else {/* similar logic without the stop */}
}

In the component where this function is employed:

this.service
  .doStuff()
  .pipe(
    // Further implementation needed here to handle response code 204
    mergeMap(result => this.httpClient.get(/* extract relevant data from the response */))
).subscribe(foo => bar())

I am seeking assistance on how to address the "TODO" section appropriately so that it can dynamically react to different response codes while also ensuring the generation of new IDs for subsequent requests. I understand that handling 204 responses poses challenges, as typical retry methods may not be directly applicable due to specific conditions regarding status codes.

Answer №1

The expand operator in rxjs is a powerful tool for recursive operations. By creating an object with the desired parameters and passing it through each iteration of http client emission, you can efficiently manage the flow of data.

of({
  continue: true,
  url: 'url to use'
}).pipe(
  expand(params => http.get(params.url, {observe: 'response'}).pipe(
    map(response => {
      // Use logic to determine next URL
      const url = generateNextUrl();
      // Use logic to decide whether to continue or stop
      const continue = decideToContinue();
      return { continue, url };
    })
  )),
  takeWhile(params => params.continue)
).subscribe();

You have the freedom to include any additional information in the parameters object, allowing customization of each iteration's processing. Simply map the http response to dictate the behavior of the subsequent call.

of({
  result: undefined,
  url: 'url to use'
}).pipe(
  expand(params => http.get(params.url, {observe: 'response'}).pipe(
    map(response => {
      // Determine if the response status is satisfactory
      const url = generateNextUrl();
      // Decide based on logic if there's a valid result
      const result = determineResult();
      return { result, url };
    })
  )),
  filter(params => params.result),
  takeWhile(params => !params.result, true),
  map(params => params.result),
).subscribe(result => {
  // Only emit once upon getting a valid result
});

Answer №2

Would employing the effects be a viable option? It seems like it could provide the most elegant solution in this scenario

Simply generate xxxx

  1. Within your service, trigger the action doStuff(id)
  2. Set up an effect to listen for the doStuff(id) action, then proceed to issue a stop request with a stored ID in the URL and eventually dispatch the stopSucceeded action
  3. Create another effect to watch for the stopSucceeded action => Issue a request with a new ID in the URL upon receiving a 200 response => Dispatch the allGoodFinish action if successful, otherwise dispatch doStuff(newId)

Answer №3

An alternative approach is to implement a recursive function using a BehaviorSubject. This allows you to utilize the filter method to check the status code of the response. If the status code is not 200, the function will call next on the subject and return false, halting the pipe and triggering a retry of the process. Once a status of 200 is encountered, the function will return true and proceed with the pipeline.

performTask() {
  if (!this.id) return EMPTY; // utilize {EMPTY} from 'rxjs';
  
  const subject = new BehaviorSubject<string>(`${STOP_URL}/${this.id}`);
  
  return subject.pipe(
    exhaustMap(url => this.httpClient.get(url, {observe: 'response'})),
    
    filter(response => {
      if(response.status === 200) {
        subject.complete();
        return true;
      }
      
      this.id = Date.now();
      subject.next(`${START_URL}/${this.id}`);
      return false;
    })
  )
}

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

Issue with Angular Material sorting (matSort) triggering ExpressionChangedAfterItHasBeenCheckedError

I have limited experience with JS, Angular, and even Node.js, but I needed to create a user interface for an application I am developing. I am learning as I go along and have managed to solve most issues on my own. However, I am currently facing an Express ...

Transfer data from an HTML form -> call the ajax load() function

My Current HTML Form Situation Currently, I have an HTML form set up with a text input field and a submit button. When the button is clicked, I want the output results to display in only a specific part of the page without refreshing the entire page. Ste ...

Implementing Basic Authentication for HTTP Requests in Angular 7

Currently, I am working with angular 7 along with Spring Boot and Spring Security. Within the Back End, I have successfully implemented basic authentication. However, while attempting to send a request from Angular with an Http Header that includes User n ...

Is it acceptable for Single Page Web Apps to have multiple requests at startup?

I've been dedicated to developing a Single Page Web App (SPA) recently. The frontend is built with BackboneJS/Marionette, while the backend is powered by Java Spring :(. However, I've noticed that the application's start time could be sluggi ...

Leverage Formidable to directly stream content to Azure Blob Storage without the need to save it in the /tmp directory

An interesting example provided by Formidable can be found here: https://github.com/node-formidable/formidable/blob/master/examples/store-files-on-s3.js. It showcases how to upload a stream to an S3 bucket without saving the content to a temporary file, wh ...

Error in THREE.js: Unable to access property 'lib' from an undefined source (THREE.ShaderUtils.lib["normal"])

I have been working on the challenges provided in the WebGL introductory book by Oreilly. Encountered a runtime error with the following code snippet. After searching online, it seems like I am the only one facing this issue. Could you please assist me in ...

Placing JavaScript at the bottom of the page, sourced from a Partial Page

I'm trying to display JavaScript code from a Razor Partial Page at the bottom of a (layout) Page. In a similar discussion on Stack Overflow about Including JavaScript at bottom of page, from Partial Views, it was suggested by user Becuzz that using a ...

What is the correct method for configuring access permissions?

I'm in the process of developing a user management system, but I keep finding myself having to check the user type for each router. router.get('/admin/settings', (req, res) => { if(admin) { //Proceed. } } router.get(&apo ...

Obtaining the ID from a URL in node.js

As a newcomer to the world of Javascript and node.js, I am venturing into creating a REST API with URLs structured as follows: /user/{userId}/docs The goal is to extract the value of {userId}, which, for instance, in the URL /user/5/docs would be 5. Wh ...

Is there a way to streamline the process of connecting multiple ajax requests automatically?

After reviewing the lower portion of my function, I realized that I need to repeat info(url_part1 + next + url_part2, function(next) { multiple times. Is there a more efficient way to accomplish this task, perhaps utilizing some type of loop? I have been b ...

Tips on storing and retrieving data between pages in Ionic 4/5: Saving data to a service and accessing it from a

Looking for assistance from the community I am trying to pass data between pages using a service in Angular. Here is the code for the parent component.ts file: import { Component } from '@angular/core'; import { ShareService } from '../sh ...

Issue arises where multiple asynchronous functions cause infinite re-rendering due to the shared loading state

Currently, I am integrating zustand 4.1.5 into my React application. Upon clicking the LogDetails tab, two asynchronous functions with identical loading state settings are triggered simultaneously, leading to an endless rerendering cycle and causing the & ...

In TypeScript, what is the return Type of sequelize.define()?

After hearing great things about TypeScript and its benefits of static typing, I decided to give it a try. I wanted to test it out by creating a simple web API with sequelize, but I'm struggling to understand the types returned from sequelize. Here ar ...

Preventing pageup/pagedown in Vuetify slider: Tips and tricks

I am currently using Vuetify 2.6 and have integrated a v-slider into my project. Whenever the user interacts with this slider, it gains focus. However, I have assigned PageUp and PageDown keys to other functions on the page and would like them to continue ...

In order to address the issue of displaying a 404 error in In-Memory Angular,

I have watched all the videos regarding the In-memory web API and diligently followed all the steps and instructions. However, I am still encountering a 404 Error. Please inform me if I missed something or made an error. I have attempted to troubleshoot an ...

How can you annotate and inherit a class method that returns an array of itself?

In the following example, I present a simplistic representation of code that may not align with standard HTML or front-end conventions. Please excuse any confusion this may cause. TL, DR I am facing challenges in specifying a return type for a method tha ...

Tips for choosing the following row in an Angular user interface grid

How can I deselect the currently selected row and select the one that follows it by clicking a button? I am using AngularHotkeys.js for this purpose. It gets even more complicated because I can sort the table with different columns. It would be helpful to ...

Transforming an array into a JSON object

I currently have an array structured like this: [ 'quality', 'print-quality: 4', 'x-dimension', 'Value: 21590', 'Value: y-dimension', 'Value: 27940', 'Value: ', 'Valu ...

Information derived from a provided URL

I am currently developing a Spotify stats app, and I am facing an issue with creating a context to store the token provided by the app in the URL when a user logs in. TokenContext.js: import React, { useEffect, useState } from "react"; // token ...

Enhance the variety of types for an external module in TypeScript

I am in the process of migrating an existing codebase from a JavaScript/React/JSX setup to TypeScript. My plan is to tackle this task file by file, but I have a question regarding the best approach to make the TypeScript compiler work seamlessly with the e ...