Effective Angular - ensuring all API calls are completed in a forEach loop before returning the final array

Struggling with the asynchronous nature of Angular, I'm faced with a challenge. My task involves looping through various cards where certain types require API calls while others do not. However, upon completion of the loop, only the cards that do not need API calls are being returned.

To illustrate how it's currently functioning:

// If color of colorCard is blue, it requires 2 API calls
// If color of colorCard is red, it requires 1 API call
// If color of colorCard is yellow, no API call needed
// For this example, let's say there's one yellow card, one blue card, and two red cards in colorCards

var buildCards = function() {
  var finishedArray = [];
  var promises = [];
  colorCards.forEach(function(e, i){
    if (e.color === 'blue') {
      promises.push(firstBlueApiCall); 
      var firstBlueIdx = 0;
      promises.push(secondBlueApiCall); 
      var secondBlueIdx = 1;
    } else if (e.color === 'red') {
      promises.push(redApiCall); 
      var redIdx = 0;
    }

    // Only push to finishedArray after completing API calls for blue or red cards
    if (promises.length > 0) {
        $q.all(promises).then(function(response) {
          if (e.color === 'blue') {
            e.firstBlueData = response[firstBlueIdx];
            e.secondBlueData = response[secondBlueIdx];
          } else if (e.color === 'red') {
            e.redData = response[redIdx];
          }
          finishedArray.push(e);
          promises = [];
        });
    // For yellow cards, simply push to finishedArray without any API calls
    } else {
      finishedArray.push(e);
      promises = [];
    }
  })
  return finishedArray;
}

The issue arises when the 'return finishedArray' statement only includes the yellow card without waiting for the red/blue cards to finish their API calls. How can I make sure all four cards are included in the final array?

Answer №1

The streamlineCards function has been streamlined:

var streamlineCards = function(colorCards) {
  var promises = [];
  colorCards.forEach(function(card, i){
    promises.push(preparePromise(card));
  });
  return $q.all(promises);
}

Creating a promise with $q.defer() is unnecessary since $q.all() already returns a promise. Moreover, a custom-made promise does not handle rejection properly. If any of the promises in $q.all() are rejected, the manually created promise using $q.defer() will not resolve.

This practice is referred to as a Deferred Anti-Pattern and should be avoided.

Similarly, adjustments can be made to the preparePromise function to eliminate the Deferred Anti-Pattern:

var preparePromise = function(card){
  var localPromises = [];

  if (card.color === 'blue') {
    localPromises.push(blueRequest1); var firstBlueIdx = promises.length - 1;
    localPromises.push(blueRequest2); var secondBlueIdx = promises.length - 1;
  } else if (card.color === 'red') {
    localPromises.push(redRequest); var redIdx = promises.length - 1;
  }

  var cardPromise;
  if (localPromises.length > 0) {
      cardPromise = $q.all(localPromises).then(function(res) {
        if (card.color === 'blue') {
          card.firstBlueData = res[firstBlueIdx];
          card.secondBlueData = res[secondBlueIdx];
        } else if (card.color === 'red') {
          card.redData = res[redIdx];
        }
        return card;
      });
  } else {
      cardPromise = $q.when(card);
  }
  return cardPromise;
}

The then method of a promise always results in a new promise that resolves with the value returned by the handler function. This approach also handles rejections correctly by passing them down the chain if the original promise is rejected, preventing issues like those seen with $q.defer().

Additionally, when there are no promises for processing with $q.all(), utilizing $q.when() can create the cardPromise.

By chaining promises through the .then method, a series of derived promises can be generated easily. The ability to resolve a promise with another promise allows deferring resolution along the chain, enabling the creation of powerful APIs.

-- AngularJS $q Service API Reference - Chaining Promises

Always opt for promise chaining over employing a Deferred Anti-Pattern.

Answer №2

After facing this challenge, here is the solution I came up with:

const handlePromise = function(data){
  let deferredData = $q.defer();
  let localPromisesList = [];

  if (data.color === 'blue') {
    localPromisesList.push(blueApiCall1); const firstBlueIndex = promises.length - 1;
    localPromisesList.push(blueApiCall2); const secondBlueIndex = promises.length - 1;
  } else if (data.color === 'red') {
    localPromisesList.push(redApiCall); const redIndex = promises.length - 1;
  }

  if (localPromisesList.length > 0) {
      $q.all(promises).then(function(response) {
        if (data.color === 'blue') {
          data.firstBlueInfo= response[firstBlueIndex];
          data.secondBlueInfo= response[secondBlueIndex];
        } else if (data.color=== 'red') {
          data.redInfo= response[redIndex];
        }
        deferredData.resolve(data);
      });
  } else {
    deferredData.resolve(data);
  }
  return deferredData.promise;
}

const generateAllCards = function() {
  const deferred = $q.defer();
  let finalCardArray = [];
  let allPromises = [];
  colorCollection.forEach(function(colorData, index){
    allPromises.push(handlePromise(colorData));
  });
  $q.all(allPromises).then(function(cards) {
    deferred.resolve(cards)
  })
  return deferred.promise;
}

I made sure to utilize promises throughout the process and it ended up being successful. Hopefully, this can assist others facing similar challenges in the future.

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

Guide to modifying text color in a disabled Material-UI TextField | Material-UI version 5

How can I change the font color of a disabled MUI TextField to black for better visibility? See below for the code snippet: <TextField fullWidth variant="standard" size="small" id="id" name=&quo ...

Leverage the power of jQuery to fetch data from a PHP script connected to a MySQL database

It might be a bit confusing, so let me clarify. I'm working on a form where users input a ticket and it gets assigned to a technician based on the service they offer. I have 3 text fields: username, email, and description of the problem. The next fie ...

Interval function not initiating properly post bullet navigation activation

Currently, I am experiencing an issue with my custom slider where the auto sliding set interval function is not working after using the bullet navigation. Despite trying to implement "setTimeout(autoSlide, 1000);", it doesn't seem to be resolving the ...

Retrieve the exact value of a key within a string

This is my unique text: let x = "Learning new things every day!"; I am utilizing the substring() method to extract a specific part of it: console.log(x.substring(9, 12)); Instead of the entire string, I only want the word 'new'. let x = "l ...

Guide on updating the form structure with ajax

Lately, I've been working on a contact module with 3 columns: name, email, and phone. There's also a +1 button that adds a new row to input another contact using ajax. However, a problem arises when updating the structure as the data in the old c ...

AngularJS is adjusting the form to be dirty, not pristine

I have created an HTML form and implemented validation for each input using AngularJS. However, I want the validation errors to be hidden initially when the page loads, and only show up once the user interacts with the form. This is how my validation is ...

Transfer attributes, but have exclusions in React

At times, we all have a common practice of wrapping DOM elements in custom components. <CustomComponet id="abc" title="abc" nonDomProp="abc" ...andsoforth /> In this example, the custom component wraps a button with pr ...

Arranging images next to each other using CSS and HTML

I've been trying to arrange four images side by side, with two on the top row and two on the bottom. I want to ensure they stay consistent across all browser sizes except for mobile. Here is what I have attempted so far: #imageone{ position: absol ...

"Troubleshoot: jQuery UI accordion not functioning properly in response to

I've encountered an issue with the accordion functionality while working with dynamic data. To address this, I'm incorporating JavaScript to generate <div> and <h3> tags for the accordion. Here is the code snippet I am using: $(&ap ...

The users in my system are definitely present, however, I am getting an error that

Whenever I attempt to retrieve all the post.user.name, an error is displayed stating Cannot read properties of undefined (reading 'name') I simply want to display all the users in my node Even though user is not null, when I write post.user, i ...

Obtaining a string value from a parallel array

Apologies for the very basic question, but I'm struggling with this. I have a word. Each letter of the word corresponds to a position in one array, and then returns the character at the same position from a parallel array (basic cipher). Here is my c ...

Updating style of element in EJS following POST request in Node.js using Express

I am working on a form page that is supposed to display a Success Alert (Boostrap) once the user fills out the form and clicks the Submit button. <form class="well form-horizontal" action="/contact" method="post" id="contact_form"> ... (input fiel ...

Load the values into the dropdown list based on the selection from the previous dropdown menu

Currently, I am working on implementing cloning functionality for select boxes. There are 3 select boxes: country, state, city. The user selects the country first which then populates the state select box based on the ID, and similarly, the city dropdown ...

Sequelize cannot locate the specified column in the database

click here for image description I encountered an issue where a column was not recognized after migrating with sequelize-cli. Even though all the columns are defined in the migration file, this error keeps appearing. Additionally, when trying to create a ...

Problem with Jquery Sticky Navigation

I've been struggling to understand this issue and can't seem to find any help. http://fiddle.jshell.net/DQgkE/7/show/ The user experience is currently a bit glitchy, but what I would like is: 1) When scrolling down the page, I want the Sticky ...

Determine if a cell is editable within the `renderEditCell` function by using MUI

I am working with a MUI data grid that contains different cell types. I am currently seeking a way to implement user permission-based editing for cells in the grid. However, I only want the permission testing to occur when a user attempts to edit a cell, r ...

What steps should be followed to make progress bar function properly?

Currently, I am delving into the world of Angular and attempting to craft a progress bar using a directive: var module = angular.module("app", []); module.directive("progressbar", function () { return { restrict: "A", link: function (s ...

How to disable scrolling for React components with CSS styling

When incorporating a React component into my HTML, I follow this pattern: <html> <body> <div id=app>${appHtml}</div> <script src="/bundle.js"></script> </body> </html> Within my application, th ...

Manage Blob data using Ajax request in spring MVC

My current project involves working with Blob data in spring MVC using jquery Ajax calls. Specifically, I am developing a banking application where I need to send an ajax request to retrieve all client details. However, the issue lies in dealing with the ...

Tips for maintaining the particles.js interaction while ensuring it stays under other elements

I have incorporated particle.js as a background element. By adjusting the z-index, I successfully positioned it in the background. While exploring solutions on the Github issues page, I came across a suggestion involving the z-index and this code snippet: ...