Is it possible to avoid sending multiple $http requests in Angular? Are there more efficient methods available?

I have developed a rather intricate method for retrieving resources using $http.

This method returns a promise and first checks my local cache to see if the resources are already available. If they are, it will return the cached data; if not, it will make the $http request. This works well once the resource has been cached, but I have several functions in the application that call this method on load, and each one of them ends up making the http request because the resources have not been returned and cached yet.

I devised a simple check to address this issue, but I can't help but think there might be a more efficient solution. I introduced a boolean variable that is set to true while the method is in the process of fetching the resource. If it is, I resolve the method with a timeout of half a second to allow the request to complete. The code snippet is provided below.

So, is there a better approach?

   var schools = [];
   var loadingSchools = false;

   function getAllSchools(forceUpdate) {
        return $q(function (resolve, reject) {
            if(loadingSchools) resolve($timeout(getAllSchools, 500));

            else{

                loadingSchools = true;

                if (schools.length && !forceUpdate) {
                    loadingSchools = false;
                    resolve(schools);
                    return;
                }

                console.log('$http: Getting All Schools - schoolService.js');

                $http.get(API_PATH + 'schools_GetAll', {cache:true})
                .success(function(result) {
                    schools = result;
                    loadingSchools = false;
                    resolve(schools);
                })
                .error(function(error) {
                    schools = [];
                    loadingSchools = false;
                    reject(error);
                });
            }
        });
    }

Answer №1

Initially, there is no need to wrap the HttpPromise within another promise. Since the success/error methods have been deprecated, it is advised to rely solely on the then() method and treat the HttpPromise as a standard promise.

To ensure that the request is only sent once, you can keep track of the initial HttpPromise created and return the same promise for subsequent function calls.

Below is a service that takes an API endpoint as an argument and guarantees that only one request is made to that API.

app.factory('$httpOnce', [ '$http', '$cacheFactory',
  function ($http, $cacheFactory) {
    var cache = $cacheFactory('$httpOnce');

    return function $httpOnce(url, options) {
      return cache.get(url) || cache.put(url, $http.get(url, options)
        .then(function (response) {
          return response.data;
        }));
    };
  }
]);

Usage

function log(data) {
  console.log(data);
}

// sends an HTTP request
$httpOnce('https://api.github.com/').then(log);
// reuses the previous promise without sending a new request
$httpOnce('https://api.github.com/').then(log);

// ...
// Upon completion of the HTTP request, both promises above are resolved
// ...

setTimeout(function () {
  // immediately resolved with cached data
  $httpOnce('https://api.github.com/').then(log);
}, 5000);

Take a look at the demo to see in the developer tools that only one request is actually made.

Answer №2

I encountered the same issue and found a solution:

app = angular.module('MM_Graph')

class TeamInfo
  constructor: ($routeParams,$rootScope, $cacheFactory, $q, API)->
    @.$routeParams  = $routeParams
    @.$rootScope    = $rootScope
    @.$cacheFactory = $cacheFactory
    @.cache         = $cacheFactory('team_Info')
    @.$q            = $q
    @.deferred      = null
    @.API           = API
    @.project       = null
    @.data          = null
    @.team          = null
    @.schema        = null

  callWithCache: (method, params, callback)=>
  
    cacheKey = "#{method}_#{JSON.stringify(params)}"
    
    if not @.cache.get cacheKey
      deferred = @.$q.defer()
      @.cache.put cacheKey, deferred.promise
      
      onDataReceived = (data)->
        deferred.resolve(data)
        
      methodParams = params.concat(onDataReceived)
      
      @.API[method].apply(null, methodParams)
      
    @.cache.get(cacheKey).then (data)->
      callback (data)

  clearData: ()=>
    @.cache.removeAll()
    @.scores   = null
    @.schema   = null
    @.data     = null
    @.deferred = null

  loadData: (callback)=>
    if not (@.$routeParams.project and @.$routeParams.team)
      return callback()

    if (@.$routeParams.project is @.project and @.$routeParams.team is @.team)
    
    else
      @.clearData()
      @.project = @.$routeParams.project
      @.team    = @.$routeParams.team
      @.projectSchema (schema)=>
        @.dataScore (scores)=>
          @.teamGet (data)=>
            @.scores = scores
            @.schema = schema
            @.data   = data

            @.deferred.resolve()

    @.deferred ?= @.$q.defer()
    @.deferred.promise.then ->
      callback()

  dataScore    : (             callback) => @.callWithCache 'data_Score'       , [@.project, @.team      ], callback
  projectSchema: (             callback) => @.callWithCache 'project_Schema'   , [@.project              ], callback
  radarFields  : (             callback) => @.callWithCache 'data_Radar_Fields', [@.project              ], callback
  radarTeam    : (targetTeam, callback) => @.callWithCache 'data_Radar_Team'  , [@.project, targetTeam ], callback
  teamGet      : (             callback) => @.callWithCache 'team_Get'         , [@.project, @.team      ], callback

  save: (callback)=>
    @.API.fileSave @.project, @.team , @.data, callback

app.service 'teamInfo', ($routeParams, $rootScope, $cacheFactory, $q, API)=>
  return new TeamInfo $routeParams, $rootScope, $cacheFactory, $q, API

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 is the best way to include the application version in an Electron project using JavaScript

While attempting to publish an update for my app, I encountered a strange error. Can anyone pinpoint the issue? (Note: Node.js is included) Error Message: Unexpected token < <script> console.log(process); let output = <h2 class="page- ...

What is the process for modifying the headers of a post request in Express when

I have a Node/Express application and I'm looking to incorporate an image upload feature into an order form. While I can successfully use the action link in the HTML form to perform a POST request, I also want my JavaScript file associated with this ...

Is there a way to identify the moment when a dynamically added element has finished loading?

Edit: I've included Handlebar template loading in my code now. I've been attempting to identify when an element that has been dynamically added (from a handlebars template) finishes loading, but unfortunately, the event doesn't seem to trig ...

Error: The function sort cannot be applied to the result of calling the calendar method on the moment object

I retrieve the data from this URL. I am looking to display the data sorted by a recent date. I attempted to use the map method to render the data and then proceeded to sort it based on the date, but encountered an error. To organize the dates, I made use ...

I'm fascinated by the way well-known websites like The Guardian are utilizing Angular, as I often notice that when I click on links, the entire page reloads

As a beginner in Angular, I recently explored popular websites that implement Angular or React. I discovered sites like The Guardian, New York Times, and Netflix. However, most of these sites have links that open in new pages, while the remaining ones ut ...

Stopping a function when another function is invoked in JavaScript

I've been immersing myself in the search for a way to halt one function if another is called or triggered in JavaScript. Initially, I believed it to be unattainable, but I'm open to having my perspective changed. My current focus lies within a t ...

Change the name of a file to a name chosen by the user while uploading it to an

I'm a beginner when it comes to node.js and I'm facing an issue that I couldn't find a solution for despite looking into various related topics. My problem involves using Node.js on the server side to upload a file and rename it based on a ...

Tips for detecting the existence of a different class within a div/ul through jquery or javascript?

This is how I have my unordered list, or "ul" element, styled. As you can observe, there are two classes defined for the ul <ul class="nav-second-level collapse"> </ul> There might be an additional class added to the same ul like so: <u ...

Tips on maintaining the numerical value while using JSON.stringify()

Having trouble using the JSON.stringify() method to convert some values into JSON format. The variable amount is a string and I want the final value in JSON format to be a number, but it's not working as expected. Even after applying JSON.stringify(), ...

Shifting the data label on a pie chart in ApexCharts - what's the trick?

I need help adjusting my data labels to make them more readable by moving them inward. It would be perfect if there is a way to dynamically center them within the slice. https://i.stack.imgur.com/bCelP.png Despite reading the documentation and trying dif ...

Dependency management in ReactJS components

I am grappling with the optimal structure for a React component that is composed of other components. Let's look at the first example: <ColorSelect id="color" label={this.state.selectLabel} trigger={{ color: "lime", text: "Lime"}} onPropagateCli ...

Using MDBootstrap for reactjs, incorporating a modal into a table for enhanced functionality

As a newcomer to the world of React.js and Material Design Bootstrap, I am attempting to load a dataset onto a table using a mock JSON file. After some trial and error, I managed to achieve a certain level of success with this task. My goal is to include a ...

Guide to creating intricate animations using a CSS/JS user interface editor

Is there an easy way to create complex animations without blindly tweaking keyframes? I'm searching for a tool or editor that can generate the necessary animation code, similar to this example: https://codepen.io/kilianso/pen/NaLzVW /* STATE 2 */ .scr ...

Setting the height of a div using CSS and navigating between views using Angular UI

Issue One: When moving one square, the border should appear and the menu should cover the entire height. I've already spent 2 hours trying to fix this, but it still doesn't fill the whole height or center vertically. Problem Two: If you make the ...

Guide on deleting objects based on their IDs when the IDs are stored in a separate array using JavaScript

I have two separate arrays. One array contains only integers as IDs (from a searchBox) and the other array contains objects, such as: categories = [1, 2, 5, 8]; items = [ { title: 'Hello', description: 'any description you want', cat ...

setTimeout executes twice, even if it is cleared beforehand

I have a collection of images stored in the img/ directory named 1-13.jpg. My goal is to iterate through these images using a loop. The #next_container element is designed to pause the loop if it has already started, change the src attribute to the next im ...

triggering a method in an Angular controller using a Google API embedded in the view

Using the Google Places Details API, I have included a Google API with a callback function called initMap in the src attribute. Here is the code snippet: <div class="tab-pane active" id="timeline"> <p class="lead">Location</p> <hr& ...

tips for achieving $translate.instant interpolation instead of concatenation

Currently, I am utilizing translate.instant and performing a string concatenation as shown below: var variable = this.$translate.instant('project.discount') + " % " + this.$translate.instant('project.applied_to') + " " +this.office; I ...

Guidelines for sending an array from a Laravel Controller through AJAX and generating a button based on the received data

Before making a selection, I click on the Color button: <form class="form form-variant"> {{ csrf_field() }} <button type="submit" class="btn btn-success che ...

Using AngularJs's filter to display a specific string when the model evaluates to true

I'm currently exploring how to create a filter that can display a specific value if the model passed in via the scope is true. For instance, when my database returns true or false for: thing.hearted, I want a filter to output "hearted" only if thing. ...