1

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.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
PGT
  • 1,468
  • 20
  • 34
  • 1
    there is no reason for the `$q.defer()` in your code, since `$http` already returns a promise. aside from that, factories aren't singletons. If your goal is to call a function multiple times but only have it make a server call once, then you should use a service, and in the service, check for data existence, then make your factory call if the data doesn't exist yet. – Claies Jun 15 '15 at 22:26
  • I will agree with you that $http returns a promise so $q.defer() not needed unless you're using it in the case of @mouse-reeve's answer, but all providers are singletons: http://stackoverflow.com/questions/18939709/when-to-use-service-instead-of-factory. – PGT Jun 16 '15 at 03:27

3 Answers3

2

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;
    }
};
Mouse Reeve
  • 267
  • 1
  • 8
  • There's a bit of a type mismatch here. `_getCategories` returns an object and I have to access the data with `_getCategories.data`, but in the first conditional, it just returns an array. Is there a cleaner way to handle this aside from checking the type? – PGT Jun 15 '15 at 22:35
  • You should resolve this in the service, not in the controller by either setting `categories` to a value that reflects what the promise returns, or having the service return a value that looks like what you set in `categories`. I think you can, in the `_getCategories` function, set `categories` they way you currently are, and then `return categories` - but you will have to check if that works. – Mouse Reeve Jun 15 '15 at 22:43
  • the promise is returned to the controller before the $http call triggers, and then when it's resolved, what the controller gets is the return from the $http call. I tried to just return `categories` but doesn't work. My resolve is in the service, as it currently is, just with your answer placed in and returning `categories` – PGT Jun 15 '15 at 22:54
  • Nevermind, it ended up working when I replaced the .success with .then. I guess it's because .success is a chain on the callback and returns the promise, but .then actually returns the results. – PGT Jun 16 '15 at 00:09
0

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.

Tanase Butcaru
  • 962
  • 1
  • 10
  • 23
-1
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;

    });
Malk
  • 11,855
  • 4
  • 33
  • 32
  • This doesn't seem to work, I believe you're returning a new object using with the value of `categories` at the time it's run, which would be before the $http call returns. I just tried this out and it didn't work. – PGT Jun 15 '15 at 22:40