0

How do I return "stories" by issuing "vm.stories = storyDataAsFactory.stories" vs. what I do now, which is "vm.stories = storyDataAsFactory.stories()" ? I've tried every combination possible without success. Furthemore, I'm able to call storyDataAsFactory.getStories without the parenthesis, which makes sense based on how I have it configured, but when I create a function that returns self.stories it doesn't work.

The below code works as specified -

storyDataAsFactory.$inject = ['$http', '$q'];

angular.module('ccsApp').factory('storyDataAsFactory', storyDataAsFactory);

function storyDataAsFactory($http, $q) {
    var self = this;
    var stories = [];

    function getStories(url) {
        url = url || '';

        var deferred = $q.defer();

        $http({method: 'GET', url: url})
            .success(function (data, status, headers, config) {
                self.stories = data;

                deferred.resolve(data);
            })
            .error(function (data, status, headers, config) {
                deferred.reject(status);
            });

        return deferred.promise;
    }

    function listStories() {
        return self.stories;
    }

    return {
        stories: listStories,

        getStories: getStories('stories.json')
    };
}

UPDATE:

I'm still having problems. Here's my exact code, which I changed per the community -

storyDataAsFactory.$inject = ['$http', '$q'];

angular.module('ccsApp').factory('storyDataAsFactory', storyDataAsFactory);

function storyDataAsFactory($http, $q) {
    var stories = [];

    function getStories(url) {
        url = url || '';

        if (url !== '') {
            var deferred = $q.defer();

            //determine if ajax call already occurred;
            //if so, data exists in cache as local var
            if (stories.length !== 0) {
                deferred.resolve();
                return deferred.promise;
            }

            $http({method:'GET', url:url})
                .success(function (data, status, headers, config) {
                    stories = data;

                    deferred.resolve();
                })
                .error(function (data, status, headers, config) {
                    deferred.reject(status);
                });

            return deferred.promise;
        } else {
            alert('URL was empty.');
        }
    }

    return {
        stories: stories,

        getStories: function(url) {
            getStories(url);
        }
    };
}

storyDataAsFactory.stories does not return anything. Remember, I've ensued that resolve fired appropriately so this is not an async. issue. I just don't get this! I've been at it for hours w/o success.

Charles Sounder
  • 591
  • 2
  • 5
  • 16
  • Followup to [*How can I gain access to a private AngularJS service Javascript variable from within a return function?*](http://stackoverflow.com/questions/27831945/how-can-i-gain-access-to-a-private-angularjs-service-javascript-variable-from-wi). – RobG Jan 08 '15 at 05:38
  • This is a different issue. The functions now works fine, but I'm trying to resolve why I need to declare my return statement for "stories" as a function and not simply return it ... as in, "stories: self.stories;" – Charles Sounder Jan 08 '15 at 05:50
  • You say "*I'm able to call storyDataAsFactory.getStories without the parenthesis*" which means you aren't calling it, you're reading the value. – RobG Jan 08 '15 at 06:18
  • I agree, but that's my problem in comprehending this. I'm actually returning an object in my factory function that links to a function w/parenthesis, which means I'm calling it via the link. e.g., return { stories: listStories, getStories: getStories('/crime/json/stories.json') }; – Charles Sounder Jan 08 '15 at 06:33
  • The problem in my comprehension is that when I do the same thing for stories, which is stories: listStories() instead of stories: listStories ... the result isn't returned. The only way I can get the result from listStories is to link it w/o the parenthesis and then invoke it when I need it in the controller. It's so bizarre! – Charles Sounder Jan 08 '15 at 06:35

2 Answers2

0

I think you are confused with Angular service and factory concept:

Lets dicuss below:

Angular service:

module.service( 'serviceName', function );
Result: When declaring serviceName as an injectable argument you will be provided
        with the instance of a function passed to module.service.

Angular factory

module.factory( 'factoryName', function );
Result: When declaring factoryName as an injectable argument you will be provided
        the value that is returned by invoking the function reference passed to
        module.factory. So if you want to access the methods of that factory then
        they should be there along with returned value.

Angular's service version of your given code will be:

schoolCtrl.service('storyDataAsService', storyDataAsService);

function storyDataAsService($http, $q) {
    var self = this;
    var stories = [];

    this.getStories = function(url) {
        url = url || '';

        var deferred = $q.defer();

        $http({method: 'GET', url: url})
            .success(function (data, status, headers, config) {
                self.stories = data;

                deferred.resolve(data);
            })
            .error(function (data, status, headers, config) {
                deferred.reject(status);
            });

        return deferred.promise;
    };
    this.stories = function(){
        // @TODO return value
    }
}

Angular's factory version:

storyDataAsFactory.$inject = ['$http', '$q'];

angular.module('ccsApp').factory('storyDataAsFactory', storyDataAsFactory);

function storyDataAsFactory($http, $q) {
    var self = this;
    var stories = [];

    function getStories(url) {
        url = url || '';

        var deferred = $q.defer();

        $http({method: 'GET', url: url})
            .success(function (data, status, headers, config) {
                self.stories = data;

                deferred.resolve(data);
            })
            .error(function (data, status, headers, config) {
                deferred.reject(status);
            });

        return deferred.promise;
    }

    return {
        stories: function() {
            // @TODO return value
        },

        getStories: getStories
    };
}
RobG
  • 142,382
  • 31
  • 172
  • 209
Suneet Bansal
  • 2,664
  • 1
  • 14
  • 18
  • I agree w/you. I get the data back just fine, it's the way by which I have to invoke the functions that bother me. I'm not grasping why I can't use parenthesis for "stories" but can use them for "getStories" in my factory function. If I put () on the end of listStories, I don't get the data back. – Charles Sounder Jan 08 '15 at 06:43
  • This is because the value that is returned by invoking the function reference passed to module.factory is in the form of json. So there is no point to associating "()" with any of the key and value. So either you can directly pass function(){} as a value or you can refer factory function refernce as a value but you can not put "()" after function reference. – Suneet Bansal Jan 08 '15 at 07:01
0

In your case self is provider of your factory storyDataAsFactoryProvider. But you need use local variable stroies, not provider object field self.stroies. I have fixed your bugs. Now it works.

UPD: if you want to use stories as field instead of getter you cannot change local variable (reference to original array). You may modify original array only.

storyDataAsFactory.$inject = ['$http', '$q'];

angular.module('ccsApp', /*XXX added []*/[]).factory('storyDataAsFactory', storyDataAsFactory);

function storyDataAsFactory($http, $q) {
    var stories = [];

    function getStories(url) {
        url = url || '';

        if (url !== '') {
            var deferred = $q.defer();

            //determine if ajax call already occurred;
            //if so, data exists in cache as local var
            if (stories.length !== 0) {
                deferred.resolve();
                return deferred.promise;
            }

            $http({method:'GET', url:url})
                .success(function (data, status, headers, config) {
                    // XXX using this code you lose original array
                    //stories = data;
              
                    // XXX instead you need to clear and to fill original array
                    stories.splice(0, stories.length);
                    data.forEach(function(x) { stories.push(x); });

                    deferred.resolve();
                })
                .error(function (data, status, headers, config) {
                    deferred.reject(status);
                });

            return deferred.promise;
        } else {
            alert('URL was empty.');
        }
    }

    return {
        stories: stories, // XXX using field instead of getter you need to keep original array

        getStories: function(url) {
            getStories(url);
        }
    };
}

// ------------ tests --------------------
describe('storyDataAsFactory.stories()', function() {
  var $httpBackend, $http, $q, storyDataAsFactory;
  
  beforeEach(module('ccsApp'));
  
  beforeEach(inject(function(_$httpBackend_) {
    $httpBackend = _$httpBackend_;
    $httpBackend.whenGET('stories.json').respond([1, 2, 3]);
  }));
  
  beforeEach(inject(function(_$http_, _$q_, _storyDataAsFactory_) {
    $http = _$http_;
    $q = _$q_;
    storyDataAsFactory = _storyDataAsFactory_;
  }));
  
  it('should return empty array before ajax resolved', function() {
    storyDataAsFactory.getStories('stories.json');
    expect(storyDataAsFactory.stories).toEqual([]);
    $httpBackend.flush();
  });
  
  it('should return filled array after ajax resolved', function() {
    storyDataAsFactory.getStories('stories.json');
    $httpBackend.flush();
    expect(storyDataAsFactory.stories).toEqual([1, 2, 3]);
  });
});

// ------------ run tests --------------------
window.onload = function() {
  var jasmineEnv = jasmine.getEnv();
  var htmlReporter = new jasmine.HtmlReporter();
  jasmineEnv.addReporter(htmlReporter);
  jasmineEnv.execute();
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine-html.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.28/angular-mocks.js"></script>
dizel3d
  • 3,619
  • 1
  • 22
  • 35
  • Thanks Dizel for sharing, this was a really big help. My last question concerns invocation of the return values. Can I make stories accessible in my controller without () ? Right now I issue a "vm.stories = ctrl.stories()" and it works, but I'd like to issue a "vm.stories = ctrl.stories". Is this possible? When I try to do as I have for getStories no data is returned and I'm forced to leave in (). – Charles Sounder Jan 08 '15 at 17:58
  • Dizel, per your example I changed my code and posted it above. For some reason it's still not working. What am I doing wrong? – Charles Sounder Jan 09 '15 at 00:37