I'm using angularjs with asp.net mvc. On my page, I have 5 dropdown lists, all populated with values from different database tables. In order to retrieve the data for all dropdowns, I'm making 5 different $http.get requests, which makes my page load time slow to a crawl. I know this is setup wrong, but not sure how to do it properly. Here's my angular code, which makes a call to an mvc action, returns the values for the dropdown & passes the results into the $scope, for the dropdown to display:

var CustomSearchController = function ($scope, $http) {    
$http.get('/Account/GetLists')
    .success(function (result) {
        $scope.listDetails = result;
    })        
 };
 $http.get('/Account/GetGenders')
    .success(function (result) {
        $scope.genderDetails = result;
    })        
 };
 $http.get('/Account/GetEthnicities')
    .success(function (result) {
        $scope.ethnicityDetails = result;
    })        
 };
 $http.get('/Account/GetRegions')
    .success(function (result) {
        $scope.regionDetails = result;
    })        
 };
 $http.get('/Account/GetAcademics')
    .success(function (result) {
        $scope.academicDetails = result;
    })        
 };

What's the correct way to go about this?

share|improve this question
2  
I cannot say this setup is wrong! . You can improve the load time by caching data in your server for these endpoints. Another option is to have a single endpoint which returns data for all 5 dropdowns in 5 different properties of the return object. – Shyju yesterday

You should use $httpProvider.useApplyAsync and pass in true.

From the documentation:

Configure $http service to combine processing of multiple http responses received at around the same time via $rootScope.$applyAsync. This can result in significant performance improvement for bigger applications that make many HTTP requests concurrently (common during application bootstrap).

This means that if true, when requests are loaded, they will schedule a deferred "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window to load and share the same digest cycle.

In short, you can avoid unnecessary digest cycles which can make a hell of a difference.

Simply pass $httpProvider into your run function and modify the provider like so:

angular.module('MyApp', [])

.run(function($httpProvider){

    $httpProvider.useApplyAsync(true);

});
share|improve this answer
    
I did not know about this, thanks. – Kildareflare yesterday
    
No problem at all, there is nothing wrong with making separate requests. I have an application with around 40 similar requests and works perfectly :) – papakia yesterday
    
@Kildareflare did this answer help answer your question? – papakia 22 hours ago
    
I was not the person who posted the question. Was just thanking you for making me aware of something I did not know :0) – Kildareflare 21 hours ago

You can use $q service for such a scenario like this:

var CustomSearchController = function ($scope, $q, $log, $http) {
    var listPromise = $http.get('/Account/GetLists');
    var genderPromise = $http.get('/Account/GetGenders');

    var ethnicCitiesPromise = $http.get('/Account/GetEthnicities')

    var regionsPromise = $http.get('/Account/GetRegions')

    var academicPromise = $http.get('/Account/GetAcademics');

    $q.all([genderPromise, ethnicCitiesPromise, regionsPromise, academicPromise])
        .then(function (responses) {
            // Here you have all the resolved datas
            // responses[0]
            // responses[1]
            // responses[2]
            // responses[3]
        }).catch(function (error) {
            $log.error(error);
            throw error;
        })

}

However I prefer to call a function of ng-change of each dropdown to retrieve data in a cascade style.

share|improve this answer
    
How does q helps in this usecase ? q is good to avoid callback tree hell. – Shyju yesterday
    
@Shyju which hell do you mean? we've got ride of hell by using promises! – HFA yesterday

Try like this: your Controller (main view):

ViewBag.regionDetailsJson = new JavaScriptSerializer().Serialize(regionDetails );

In your view:

var regionDetailsJson = $.parseJSON('@Html.JsonRaw((string)ViewBag.regionDetailsJson )');

And than in your angular controller:

$scope.regionDetailsJson = regionDetailsJson;
share|improve this answer

Using promises as suggested by HFA is a more Angular approach, but it will still result in 5 calls to the server; so will not really help improve your page load performance - i.e you will still be waiting for the 5 promises to resolve before you get your data.

One thing you could try is to formulate this lists when the page served by your MVC Account action is loaded and save these to the page model. You can then access this client side, in a script block, assigning them to a (global)variable, which you can then access in Angular.

I've not tested this but have you this approach in the past. As a guide the steps you would need to follow are:

In your AccountController

public ActionResulst Index() {
    var model = new AccountModel {
     Lists = GetLists(),
     Ethnicities = GetLists()
    }
  return View(model);
}

Then on your Razor Page

@model AccountModel
    <script>
        window.lists= "@(Model.Lists)";
        window.ethnicities = "@(Model.Ethnicities )";
    </script>

Then in your Angular

var CustomSearchController = function ($window) { 

var lists = $window.lists;
var ethnicities  = $window.ethnicities;
}

You may need to convert the results to JSON using JSON.Parse

share|improve this answer

There is nothing wrong with your approach. You are following the separation of concern, and single responsibility.

If you get the date from Model or ViewModel from ASP.Net MVC View, you cannot Unit Test the Angular Services easily.

Another approach is to use resolve. It basically load the data first before a route executes. FYI: It will not improve the overall loading time, although you can display page loading icon.

Demo for Angular 1.5 Route Resolve at Plunker

(function () {

    angular
      .module("app", ['ngRoute'])
      .config(function($routeProvider){
        $routeProvider
          .when("/home", {
              template: `<main 
                promise-followers="$resolve.promiseFollowers"></main>`,
              resolve: {promiseFollowers: function ($http) {
                    return $http.get("https://api.github.com/users/octocat/followers")
                      .then(function(result) { 
                        return result.data;
                      }, function(result) { 
                        console.log(result);
                      });
                }
              },
          })
          .otherwise({ redirectTo: "/home" } );
      })
      .component("main", {
        template: `<h3>Demo Angular 1.5 Resolver</h3>
        
        <p>Promise Data from Resolve : 
          <select ng-model="$ctrl.selected" 
            ng-options="option.id as option.login for option in $ctrl.promiseFollowers"></select>
        </p>        
        
        <h4>Regular Selector Selected: {{$ctrl.selected}}</h4>`, 
        bindings: {
          promiseFollowers: '='
        },
        
        controller: function(){
          
        }
      });
      
})();
<!DOCTYPE html>
<html ng-app="app">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <link data-require="[email protected]" data-semver="3.3.6" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css" />
    <script>document.write('<base href="' + document.location + '" />');</script>
    <script src="https://code.angularjs.org/1.5.7/angular.js"></script>
    <script src="https://code.angularjs.org/1.5.7/angular-route.js"></script>
    <script src="app.js"></script>
  </head>

  <body>
    <ng-view></ng-view>
  </body>

</html>

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.