AngularJS : Access stored data in a factory after a $http call
Asked Answered
P

2

6

I am trying to build a factory to act as a staging area for my database models, as well as an api to perform basic CRUD calls. I want to be able to access data by storing it in a service or a factory, and keep api methods along with it so I can perform actions like these in the controller.

$scope.folders =  Folders.data(); // for a factory
$scope.folders = Folders.data; // for a Service

Folders.create({name: "My Stuff, $oid: { 5fwewe033333 }, user_id: CurrentUser.id"});

Currently I am using the Folder factory like this in the controller.

Folders.foldersData().success( function(data, status) {
   $scope.folder = data;
})
.error( function(data,status) {
   Flash.warning("There was a problem fetching your data");
});

I know I can just have a promise resolved in the controller, but with the size of the project I'm working on, I like the idea of accessing the Folders model in a service, with out having to make a server call to sync the data every time I make a change.

angular.module('cmsApp')
  .factory('Folders', function($http, $q){

    var folders = {};
    var messageWarn = "Upload Retrival Failed.";


    return {
      get: function(){
        var defered = $q.defer();

        $http.get('/folders').success( function ( data, status ) {
          defered.resolve(data);
        })
        .error( function ( data, status ) {
          defered.reject();
          Flash.warning(message_warn);
        });
        defered.promise.then( function (promise)
          folders = promise;
        });
      }, 
      data: function (){
      return folders;
    },
  }               
});

My problem is that I can't keep the folders object to persist after I call Folders.get(). It always comes back after I call Folders.data() as an empty object.

Is there a way to keep this data stored in the Factory as a up-to-date representation of the Folder model that is not dependent on hitting the server every time?

Running angular 1.2.3, on a Rails 4 API.

Peden answered 28/1, 2014 at 20:36 Comment(1)
Yes, I'll make an example. I think the main thing you're confused about is that a factory creates a service and then is done. You can't access factory, so to speak. Only services.Premises
B
10

You can store the promise in the service as an object on the service. I forked the expanded demo above to demonstrate http://plnkr.co/edit/2HqQAiD33myyfVP4DWg3?p=preview

As with the previous examples, the http call is only made once but this time the promise is added to the folders item on the service object which gets created by the factory.


app.factory('myService', function($http, $q) {

  return {

    myObject: '',

    get: function() {
      // Create the deffered object
      var deferred = $q.defer();

      if(!this.myObject) {
        // Request has not been made, so make it
        $http.get('my-file.json').then(function(resp) {
          console.log('Making the call!');
          deferred.resolve(resp.data);
        });
        // Add the promise to myObject
        this.myObject = deferred.promise;
      }
      // Return the myObject stored on the service
      return this.myObject;

    }

  };

});
Blither answered 12/2, 2014 at 0:52 Comment(5)
I added an additional plunkr demonstrating a more usable approach that enables you to re-use the request code but still only makes the request once per controller. See plnkr.co/edit/2HqQAiD33myyfVP4DWg3?p=preview Personally, I encapsulate the makeRequest object as it's own service with more flexibility (such as HTTP GET, POST, DELETE, etc.) and inject that service into all other services that will be hitting my api.Blither
I don't see how this really differs from my answer. There are plenty of ways to present the same concept. Further, you're using the promise anti-pattern by manually creating a promise rather than using the promise returned by the $http call.Premises
@Blither Your links are dead ? :)Miscarriage
@AndersMetnik The only real difference between my answer and this one is this one uses the promise anti-pattern (which is bad), and I fetch the data sooner (minor detail - easy to change). I'd be glad to help clarify what about my code you don't understand. Just let me know.Premises
@ All. I've done some research and it seems that this pattern indeed is faulty. I wish I could retract my upvote. If anyone sees this, please use answer from @Premises instead. It is more simple, and it is more reliable.Miscarriage
P
2

In this example, the service essentially IS the data. The first time the service is injected, a promise is created and the call goes out. The service is actually the promise of that data, but when the call comes back, the promise is resolved with the data. So, the second, third, etc time the service is injected, the call isn't made again - the factory has already done its job and returned the service (the promise in this example).

Live demo (click).

var app = angular.module('myApp', []);

app.controller('myCtrl', function($scope, myService) {
  myService.then(function(data) {
    $scope.data = data
  })
});

app.factory('myService', function($http, $q) {
  //this runs the first time the service is injected
  //this creates the service

  var promise = $http.get('my-file.json').then(function(resp) {
    return resp.data;
  });

  return promise;

});

Here's an expanded demo in which I use the data twice. Note the console log - the call is only ever made once. In this demo, I made the service into an object with a get method that returns the promise. I just wanted to demonstrate another way this technique could be implemented.

app.factory('myService', function($http, $q) {

  console.log('Making the call!');
  var promise = $http.get('my-file.json').then(function(resp) {
    return resp.data;
  });

  var myService = {
    get: function() {
      return promise;
    }
  };

  return myService;
});
Premises answered 28/1, 2014 at 20:44 Comment(5)
Thanks @m59, But what if I wanted to store the resolved promise IN the service and be able to manipulate the data without having to hit the server again? So I could make calls like Folders.data[1] = { folder_name: "New Folder" }Peden
I have the same question I want to store the resolved promise in the service as wellBarrettbarrette
@Barrettbarrette I'm not sure what problems you're having. I demonstrated two ways that this can be done and they work fine.Premises
They save the returned promise but I would like to save the data itself.Barrettbarrette
@Barrettbarrette Effectively, it's the same thing. What you're talking about can't work out semantically. Sure, you could apply the data directly to the service when the call comes back, but that doesn't give you any control over how the data is used. Your consumers of that data would end up trying to use the data before the ajax call returns. When accessing data from an asynchronous source, it is proper to return the promise of it. If the data is already there, that just means the then function will fire immediately.Premises

© 2022 - 2024 — McMap. All rights reserved.