use angular factory to hold a value for rest of application to access to minimize server calls

324 views Asked by At

I have the following factory:

angularModule
  .factory('ArticleCategoryService', function ($http, $q) {
    // Service logic
    // ...

    var categories = [];

    var _getCategories = $http.get('/api/articles/category').success(function (_categories) {
      categories = _categories;
    });
    // .error( function (data, status, headers, config) {
    // });

    // Public API here
    return {
      getCategories: function () {
        var deferred = $q.defer();
        deferred.resolve(_getCategories);
        return deferred.promise;
      }
    };
  });

and this is the section that calls this service in the controller:

// Calls the getCategories function from the ArticleCategory Service,
    // Will return a promise
    ArticleCategoryService.getCategories()
      .then(function (categoriesResult) {
        $scope.categories = categoriesResult.data;
      }, function (err) {
        console.log(err);
      });

This works but there will be a GET call to the server every time user comes back to this view/state and the categories object that belongs to the factory is never used.

I'm trying to make it so that it will return the categories variable in the factory singleton, and have it initialize on site load (or from first GET call).

But if I just return categories when user calls getCategories, it will return nothing since we need time for the $http call.

3

There are 3 answers

4
Mouse Reeve On BEST ANSWER

Check if categories is defined, and resolve the promise with the variable rather than the GET request if it is:

return {
    getCategories: function () {
        var deferred = $q.defer();
        if (categories.length > 0) {
            deferred.resolve(categories);
        } else {
            deferred.resolve(_getCategories);
        }
        return deferred.promise;
    }
};
1
Malk On
angularModule
  .factory('ArticleCategoryService', function ($http) {
    // Service logic
    // ...

    var categories = [];

    $http.get('/api/articles/category').success(function (_categories) {
      categories = _categories;
    });


    // Public API here
    return {
      categories: categories
    };
  });


angularModule
   .controller('ControllerMain', function($scope, ArticleCategoryService) {

       $scope.categories = ArticleCategoryService.categories;

    });
2
Tanase Butcaru On

I'm doing exactly same thing on my app. I have a main module and a main controller within it that wraps any other controllers, so its scope is persistent over views.

In this main controller you could assign the factory's getCategory() data to a scope variable and then you could use that in your entire app, because the scope will be inherited to child scopes.

angularMainModule
  .factory('ArticleCategoryService', function ($http) {
      var ArticleCategoryServiceMethods = {};

      //fn: getCategories
      ArticleCategoryServiceMethods.getCategories = function(fromWhere){
         return $http.get(fromWhere)
                 .then(function(res){
                     return res.data;
                 }, function(err){
                     return err.status;
                });
      }

      return ArticleCategoryServiceMethods;
}

angularMainModule
   .controller('MAINCTRL', function($scope, ArticleCategoryService) {

    //get all categories
    $scope.categories = ArticleCategoryService.getCategories('/api/articles/category');

   //... the rest of the main ctrl code ... //

}

... when you define the main module, make sure you inject the rest of your modules in it

var angularMainModule = angular.module('angularMainModule', [
    'ngRoute',
    'ngTouch',
    'ngAnimate',
    //< ng - etc >,
    'Module1',
    'Module2',
    //< module - n >
]);

...and the markup (i'm bootstrapping my angular app manually, but you could add the ng-app="angularMainModule" attribute on the html tag if you're doing it that way):

<html ng-controller="MAINCTRL">
<!-- head data -->
<body>
<div id="page" ng-view></div>

If you want to make sure data is loaded before your app opens the main page, you could add that service call in the routeProvider block of your app (on the default route), so when the MAINCTRL will be loaded the data will be already there, ready to be assigned.