2

I'm trying to get a factory JSON response, save it in a variable, in order to be ready to be called from 2 different controllers.

Here bellow I paste the code I'm using:

storyFactory.js

var story = angular.module('story.services', []);

story.factory('storyAudio', [ '$http', function ($http) {

  var json = {};

  function getJSON(story_id, callback) {  
    $http({
      url: 'https://api.domain.co/get/' + story_id,
      method: "GET"
    }).success(function (data) {
      json = data;
      callback(data);        
    });
  };

  return {  
    getSubaudios: function(story_id, callback) {
      getJSON(story_id, function(result) {
        callback(result);
      });
    },
    getTopbar: function(callback) {
      callback(json);
    }
  };
}]);

StoryCtrl.js

var storyCtrl = angular.module('story', ['story.services']);

storyCtrl.controller('storyCtrl', [ 'CONFIG', '$stateParams', 'storyAudio', function(CONFIG, $stateParams, storyAudio) {

  var data = this;
  data.story = {};

  storyAudio.getSubvideos($stateParams.story_id, function(response) {
    data.story = response;
  });

}]);

TopbarCtrl.js

var topbarCtrl = angular.module('topbar', ['story.services']);

topbarCtrl.controller('topbarCtrl', [ 'CONFIG', '$stateParams', 'storyAudio', function(CONFIG, $stateParams, storyAudio) {
  var data2 = this;
  data2.story = {};

  storyAudio.getTopbar(function(response) {
    data2.story = response;
  });    
}]);

The problem is in my TopbarCtrl response I'm receiving an empty data2.story when I call it in the HTML.

The reason is because it doesn't have a callback of the $http response, so it prints the var json with the actual status, that is an empty object.

How could I load the second controller when the variable has content?

Thanks in advice.

DevStarlight
  • 784
  • 12
  • 29
  • You've run in to a race condition. It seems you might be able to load the JSON data on demand but you'd need to supply the required `story_id` parameter to both `getSubaudios` **and** `getTopbar` – Phil May 10 '16 at 00:48
  • @Phil actually what I'm trying to do is just to request the server once, receive a `json` response and manage the data between 2 controllers that are called by 2 ui-view in the same `$stateProvider`. – DevStarlight May 10 '16 at 01:03

3 Answers3

3

I think the best you can do in this case is load the data via getSubaudios and provide a reference to the data for other controllers to use. Something like this...

story.factory('storyAudio', function($http) {
    var factory = {
        story: {}
    };

    factory.getSubaudios = function(story_id) {
        return $http.get('https://api.domain.co/get/' + story_id).then(function(response) {
            return angular.extend(factory.story, response.data);
        });
    };

    return factory;
})

Using angular.extend() instead of directly assigning a value to the factory's story property maintains any references that may be established before the data is loaded.

Then you can load the data via

storyCtrl.controller('storyCtrl', function(storyAudio) {
    var data = this;
    storyAudio.getSubaudios($stateParams.story_id).then(function(story) {
        data.story = story;
    });
})

and directly reference the story data by reference in your controller

topbarCtrl.controller('topbarCtrl', function(storyAudio) {
    this.story = storyAudio.story;
})
Phil
  • 157,677
  • 23
  • 242
  • 245
1

I think I'm understanding correctly, but let me know if not.

There are two issues I'm seeing. The first is that there is a typo in your StoryCtrl.js file. You are calling "storyAudio.getSubvideos" but the function is called "getSubaudios" in your factory.

Even with that typo fixed, the issue could still technically happen. It all really depends on how quickly the promise returns from the first call. Unfortunately, promises are asynchronous, so there is no guarantee that the "json" variable will get set before the second controller tries to get it.

In order to resolve this, you need to ensure that the first call is finished before trying to access the "json" variable you have on the service. There are probably a few different ways to do this, but one that comes to mind is to actually return and store the promise in the service like so...

  var dataPromise;
  function getSubaudios(story_id){
        if(!dataPromise){
          dataPromise = $http({
            url: 'https://api.domain.co/get/' + story_id,
            method: "GET"
          });
        }
        return dataPromise;
  }

  return {  
    getSubaudios: getSubAudios
  };

Then in your controllers, you can just call the service and use .then to get the data out of the promise when it returns...

storyAudio.getSubaudios($stateParams.story_id).then(function(response){
   data.story = response; //or data2.story = response;
});

Here is a plunkr example. I've used the $q library to simulate a promise being returned from an $http request, but it should illustrate the idea.

sourdoughdetzel
  • 664
  • 3
  • 6
  • 1
    Problem with this approach is the `topbarCtrl` would need to know the `story_id` in order to properly call `getSubaudios`. Also, given `story_id` comes from the `$stateParams`, I assume OP would want to load different data for each state. – Phil May 10 '16 at 01:32
  • True, missed the $stateParams thing. It's not particularly clear what the OP is going for here without seeing more of the app, but your approach with the exposed story data on the factory is definitely a better one. – sourdoughdetzel May 10 '16 at 01:58
  • I wouldn't necessarily say *"better"*. My approach is open to external data manipulation whereas a reference to the promise should insulate that somewhat – Phil May 10 '16 at 03:04
1

Similar to Phil's answer. (Angular extend, or angular copy keeps the references the same in both controllers. If you don't want to put watchers in both controllers to keep track if the value changes.) Several methods here: Share data between AngularJS controllers.

You could also bind the object you are returning directly to the update-function. That way the references stay intact.

storyServices.factory('storyAudio', ['$http', function($http) {
  return {
    data: { json: '' },
    getSubaudios: function(story_id) {
      $http.get('http://jsonplaceholder.typicode.com/posts/' + story_id)
        .then(function(response) {
          this.data.json = response.data.body;
        }.bind(this));
    }
  };
}]);

var storyCtrl = angular.module('story').controller('storyCtrl', ['$scope', 'storyAudio', function($scope, storyAudio) {
    $scope.data = storyAudio.data;
    storyAudio.getSubaudios(2);
}]);

var topbarCtrl = angular.module('story').controller('topbarCtrl', ['$scope', 'storyAudio', function($scope, storyAudio) {
    $scope.data2 = storyAudio.data;
}]);

Plunk here: http://plnkr.co/edit/auTd6bmPBRCVwI3IwKQJ?p=preview I added some scopes to show what happens.


Sidenote:

I think it's straight out dishonest to name your non-controller "storyCtrl" and then assign it a controller of its own:

var storyCtrl = angular.module(...); // Nooo, this is not a controller.
storyCtrl.controller(...);   // This is a controller! Aaaah!

Another sidenote:

.success() is the old way of doing things. Change to .then(successCallback) today! I dare to say it's the standard convention for promises. https://docs.angularjs.org/api/ng/service/$http#deprecation-notice

Community
  • 1
  • 1
ippi
  • 9,857
  • 2
  • 39
  • 50
  • 1
    Since I'm new at JavaScript and AngularJS, I took notes of all the mistakes I have commit. Both options were correct and fine for the problem. – DevStarlight May 10 '16 at 02:57