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

Perform the function prior to making any adjustments to the viewmodel attributes

Within my view, I am showcasing employee details with a checkbox labeled Receive Daily Email. When a user interacts with this checkbox, I want to trigger an AJAX function to validate whether the user is allowed to modify this setting: If the result is tru ...

Navigating the ropes of push-pull functionality in Bootstrap 5

How can I utilize push and pull in Bootstrap version 5.0.0-beta2? In Bootstrap 3, my code looks like this: <link href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet" /> <div class="row"> <d ...

Updating a particular element within an array in Firestore

Hello everyone, I'm currently working with Google Firestore and Angular. I am facing a challenge where I need to update a particular element within an array. I have experimented with various solutions but unfortunately, none of them have been success ...

Encountering a 403 Error when Configuring POST Requests in Angular 6 Proxy

Currently troubleshooting a local Angular 6 application with the command ng serve --proxy-config proxy.conf.json. Here's a snippet of my proxy.conf.json file: (target has been changed for security purposes) { "/**": { "target": "http://my-main- ...

Upgrade Angular to the most recent version available

Our organization is looking to upgrade our existing Angular version from v5 to either the latest version 12 or 13. After conducting some research, I believe that transitioning directly from 5 to 12-13 may be too significant. What would be the most effect ...

Achieve instant redirection upon user logout with Angular and Meteor

Within a Meteor application utilizing Angular, there exists a specific state that mandates the user to be logged in. Currently, this requirement is implemented using $meteor.requireUser() function run($rootScope, $state) { $rootScope.$on('$state ...

The scope of a JS array is being lost in Firebase

The Firebase data structure looks like this: -users --demo1 ---conid:1 -election --election1 ---conRegex:1 --election2 ---conRegex:1 Here is the code to retrieve election1 and election2: var conid; var conRegex; var electionArr = []; if(uidA ...

Present pop-up messages in the most sophisticated manner

I have successfully created an AngularJS app that is functioning well. Now, I am faced with the challenge of displaying different pop-ups based on specific conditions being met. I am unsure of the best approach to take. Currently, I am considering two op ...

Iterating over an array while postponing a function

My goal is to create a continuous loop through an array of number values. These values will be used as delay parameters in a setInterval function, triggering another function each time. Here's what I've come up with: HTML: <p>On</p> ...

Tips for eliminating duplicate entries in ag grid using Angular

Is there a way to eliminate the recurring assetCode entries in ag grid? The PRN and PRN1 values seem to be repeating unnecessarily. Check out the code below: list.component.ts ngOnInit() { this.rowData.push( { 'code': 'Machi ...

No Angular applications are currently functioning in any web browsers

https://i.stack.imgur.com/uZph5.pngOne of my Angular applications is having trouble running in the browser when I use the ng serve command. Each time I try, I receive the following message in the browser: This site can’t be reached. localhost took too ...

What is the process of connecting marionette rendered views to the DOM?

I am encountering a challenge with my simple setup using Marionette that I am trying to resolve. My issue lies within a view containing a collection: var MyItemsView = Marionette.View.extend({ template: "#some-template" }); var view = new MyItemsVie ...

Encountering a NgForm provider error in Angular 4.4.6 development mode

UPDATE: Identifying the root of the issue has led me to search for a suitable solution. NOTE: This complication is specific to development mode (not production, and not utilizing AOT). The "Update" resolution I am implementing can be found here. In an a ...

bootstrap thumbnail displayed without a border

I have decided to incorporate Bootstrap into my latest project: <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS ...

AngularJS powered edit button for Laravel route parameter

I have a data list that needs to be edited using an edit button. When clicking the edit button, I need to send the ID to a Laravel controller in order to fetch the corresponding data. The initial listing was created using Angular JS. <a class="btn" hr ...

Alexa Skills Issue: Problem with Playing AudioPlayer HLS Stream URL

I'm currently using the "AudioPlayer" feature from the "Alexa Skill Kit" to stream an HLS audio format URL. No errors are showing up from AWS Lambda or the Developer Portal. I've been testing it with Silent Echo (). Alexa can play MP3 URLs and so ...

NodeJS error: The 'error' event was not properly handled, resulting in a throw er

I've developed a basic web application using React, node-postgres, expressJS, and PostgreSQL DB. The app includes two input fields and a button. Upon clicking the button, the values are saved in the database. While running the ExpressJS server with ...

Populate a JSON table in React with checkboxes and automatically mark them based on the JSON data

I'm currently working on creating a React table using JSON data like this: [ { "Id_side": 123, "Name_side": "R4", "Name_cycle": "C1" }, { "Id_side": 345, "Name_side": "M1", "Name_cycle": "C2" ...

Searching for hidden elements within a div using a filter option

An accordion is located inside a div and a search box has been added to the div with the intention of serving as a search filter. Some accordion elements are visible within the div while others are hidden. The problem arises when trying to make the filter ...

Tips for managing the ever-evolving language state in expressJS

I am currently working on a project utilizing nodeJs, handlebars, and the expressJs framework. I have implemented a language change functionality using the i18n-express module. This module adds a query string at the end of the URL when changing languages. ...