What is a more efficient way to write nested subscribe in Angular?

I am a beginner with RxJS and I'm interested in learning how to write clean code using it. I currently have a nested subscription that I've been trying to refactor without success.

firstMethod() {
  this.testMethod(name)
  console.log(this.currentPerson)
}

testMethod() {
  this.myService.search(name).subscribe(response => {
    if(result) {
      this.currentPerson = result
    } else {
        this.myService.create(name).subscribe(result => {
          const person = {id: result.id, name: name}
          this.currentPerson = person
        })
      }
   })
}

Unfortunately, even though the code is messy, there seems to be an issue after the 'else' statement as the console.log displays 'undefined'. Any suggestions on how to resolve this?

Answer №1

If you want to efficiently handle nested subscribes, employing one of the "Higher Order Mapping Operator" is recommended as they offer several advantages:

  1. They map the incoming value to another observable.
  2. They subscribe to it, ensuring that its values are emitted into the stream.
  3. They manage unsubscribing from these "inner subscriptions" automatically.

For this scenario, switchMap is a good option because it permits only one inner subscription at a time. This means that whenever myService.search(name) is invoked, a new inner subscription for myService.create(name) is activated, with the previous one being automatically unsubscribed.

@Rafi Henig's response provides a clear example of how to implement this solution effectively.

  • Note the usage of .pipe(). You can apply transformations to your observable output using pipeable operators without actually subscribing.

I recommend avoiding subscribing within your testMethod(), and instead returning an observable. Additionally, consider giving 'testMethod()' a more descriptive name like 'getPerson()' for better clarity in future discussions:

getPerson(name: string): Observable<Person> {
  return this.myService.search(name).pipe(
    switchMap(result => {
      return iif(
        () => result,
        of(result),
        this.myService.create(name).pipe(
          map(({ id }) => ({ id, name }))
        )
      )
    }),
    tap(person => this.currentPerson = person)
  );
}

The console.log displays undefined. Any suggestions on how to resolve this?

1  firstMethod() {
2    this.getPerson(name)
3    console.log(this.currentPerson)
4  }

The reason for the 'undefined' output is due to the asynchronous nature of the code. Line 2 is executed, followed immediately by line 3. However, since the async operation hasn't completed yet, 'this.currentPerson' remains unset.

Since our 'getPerson()' method now returns an observable, we can subscribe and utilize your 'console.log()' action inside the subscription:

1  firstMethod() {
2    this.getPerson(name).subscribe(
3       () => console.log(this.currentPerson)
4    )
5  }

To simplify further, we can eliminate the need for 'this.currentPerson' altogether, as the person is emitted via the stream:

1  firstMethod() {
2    this.getPerson(name).subscribe(
3       person => console.log(person)
4    )
5  }

Considering your desire to...

Learn how to write clean code utilizing it effectively

A neat approach would involve defining your 'person result' as an observable and discarding 'this.currentPerson':

person$ = this.getPerson(name);

Thus, you now have 'this.person$', which can be subscribed to and always holds the most up-to-date person data. Manually updating 'this.currentPerson' becomes unnecessary.

Almost there... Just remember to account for changes in the search term.

Assuming the search term 'name' originates from a form control input:

When utilizing Reactive Forms, the input value serves as an observable source, allowing us to link our 'person$' to the search term:

searchTerm$ = this.searchInput.valueChanges();

person$ = this.searchTerm$.pipe(
  switchMap(searchTerm => this.getPerson(searchTerm))
);

getPerson(name: string): Observable<Person> {
  return this.myService.search(name).pipe(
    switchMap(result => {
      return iif(
        () => result,
        of(result),
        this.myService.create(name).pipe(
          map(({ id }) => ({ id, name }))
        )
      )
    })
  );
}

We've defined two distinct observables, but haven't subscribed yet! Now, we can make use of the async pipe in our template to manage the subscription, maintaining simplicity in our component code.

<p *ngIf="person$ | async as person">
  We found {{ person.name }} !
</p>

This might be lengthy, but I hope it illustrates how to transform outputs using pipeable operators and establish one observable based on another.

Answer №2

Implement the use of switchMap to generate a new Observable based on the value of result by leveraging the IIF operator as illustrated in the code snippet below:

this.myService.search(name)
  .pipe(
    switchMap(result => {
      return iif(
        () => result,
        of(result),
        this.myService.create(name).pipe(map(({ id }) => ({ id, name })))
      )
    })
  )
  .subscribe(person => {

  })

Alternatively:

this.myService.search(name)
  .pipe(
    switchMap(result => {
      if (result) return of(result);
      else this.myService.create(name).pipe(map(({ id }) => ({ id, name }));
    })
  )
  .subscribe(person => {

  })

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

What exactly does the question mark represent in the code structure as indicated in VSCode?

When looking at the image, you can see that in the description of done(), VSCode indicates the type of parameters using a colon error: any or sometimes with a question mark and colon user?: any. So, what exactly is the distinction between these two ways o ...

What causes the error "Why am I receiving a "Cannot read property 'length' of undefined" when looping through a pug template?

Currently, I am in the process of developing a project using Node and Express. My objective is to have the home page display signup and login links in the nav bar when the user is not logged in. Initially, everything seemed to be working fine, and I was ab ...

What is the best way to refine the dataSource for a table (mat-table) using ngx-daterangepicker-material?

I am facing a new challenge and feeling unsure about how to approach it. The issue at hand is filtering a table based on the date range obtained through the ngx-daterangepicker-material library. This library triggers events that provide a start date and a ...

I am experiencing challenges with utilizing moment and angular filters

I was able to get this code working perfectly before the recent jsfiddle update. However, now it seems to be causing issues. Any assistance would be greatly appreciated. Let's start with the HTML code: <div ng-app="app" ng-controller="ctrl"> ...

Having difficulty grasping the significance of the data received from the API response

Currently, as I am working on my personal Portfolio for a Web Developer course, I have encountered an issue with correctly implementing my API to retrieve information from the database. Previously, I faced no problem when using a .json file, but now, I am ...

What are the necessary steps for incorporating Payment/Bank transactions into an e-commerce platform?

Right now, I'm utilizing HTML, CSS, jQuery, and some JavaScript to develop a website. As I work on creating the store section, I find myself unsure of how to incorporate payment methods and all related features. Are there any free "pre-built" systems ...

Tips for updating information when a button is chosen

Hello everyone, I need some help with a form that has three select buttons. The button labeled "Distribute" is already selected when the page loads, and it contains information about full name, password, and location. How can I use JavaScript to create a c ...

Experience the magic of animated number counting using RxJs

I am working on my Angular application and I want to implement a feature where the records from HTTP get requests are animatedly counted. However, when using RxJs, the response is returned too quickly and I only see the final result immediately. I attemp ...

"PHP script for submitting a form with a post button

Can anyone help me figure out why the submit function isn't working? I've been trying to solve the problem with no luck so far. Any ideas on what might be causing this issue? <?php if(isset($_POST['submit'])){ echo ("Submit fun ...

refusing to display the pop-up in a separate window

Hi there, I'm having an issue with a link that's supposed to open in a pop-up but is instead opening in a new tab. I'd like it to open in a proper pop-up window. <button class="button button1" onclick=" window.open('te ...

Using Angular, Typescript, and ngxs to manage state observables, one may wonder what exactly a variable ending with an exclamation mark (!) signifies. An example of this can be seen in the following code snippet:

Within my TS file, a declaration is present: import { Select } from '@ngxs/store'; @Injectable() export class someService { @Select(someSELECTOR) varName$!: Observable<someType[]>; elements$ = this.varName$.pipe( map(elements => e ...

Updating the state of a Next.JS router component with React.JS: A step-by-step guide

I've implemented a toggleswitch in my app that changes its state based on the dynamic URL parameters. For instance, if the URL includes a parameter named followType set to following (e.g. https://www.example.com/home?followType=following), I want the ...

the async function fails to run

fetchData = async () => { try { //Accessing data from AsyncStorage let car = await AsyncStorage.getItem('CAR') this.state.DatabaseCar=[]; this.state.DatabaseCar = JSON.parse(car); alert(this.state.Da ...

Error: 'delay' is not recognized as a valid function

I'm encountering an error message that reads as follows: $("div.valid_box").html(data.message).css("margin-left", "145px").css("width", "520px").show().delay is not a function [Break On This Error] $(...elay(10000).hide("slow", function() { within m ...

Warning: The core schema has detected an unknown property `color` for the component or system `undefined` in Aframe + Vuejs. This issue was flagged within 10 milliseconds in

I am facing some challenges trying to integrate Aframe and vuejs seamlessly, as the console is displaying warning messages. It seems like Aframe is validating the attribute values before vue has a chance to modify them. Warning messages core:schema:warn ...

Interact with Circles Through Mouse Movements with d3.js Animation

I am currently utilizing the d3.js library and facing a challenge in meeting the client's requirements. The client has requested for "circles to turn black" and "follow" the mouse when hovered over. I am unsure if d3.js library supports such a featu ...

Discover the method for obtaining a selected element in a bootstrap dropdown that is dynamically populated

Similar to the question asked on Stack Overflow about how to display the selected item in a Bootstrap button dropdown title, the difference here is that the dropdown list is populated through an ajax response. The issue arises when trying to handle click ...

Steps for converting an HTML form into a sophisticated JavaScript object

Is it possible to transform a form into a complex JavaScript object based on a structured form layout? I am not sure if there is a better way to accomplish this, but essentially what I am looking for is the following scenario: <form> <input n ...

How can PHP be used to decode JSON and transmit it to Javascript?

I am aiming to utilize the Twitter API in order to dynamically populate slides on a webpage with recent tweets without needing to refresh the entire page. Currently, my approach involves making an AJAX call every few seconds from a JavaScript function on ...

Guide on attaching an onclick event to a button created with a string in Next.js?

<div onClick={(event) => this.addToCart(event)}> <ReactMarkdownWithHtml children={this.props.customButton} allowDangerousHtml /> </div> In my current situation, I am facing an issue with the code above. The button is being rendered ...