I am having a problem getting data from a service populated into my view. I have a service defined as such
app.factory('nukeService', function($rootScope, $http) {
var nukeService = {};
nukeService.nuke = {};
//Gets the list of nuclear weapons
nukeService.getNukes = function() {
$http.get('nukes/nukes.json')
.success(function(data) {
nukeService.nukes = data;
});
return nukeService.nukes;
};
return nukeService;
});
and my controller
function NavigationCtrl($scope, $http, nukeService){
/*$http.get('nukes/nukes.json').success(function(data) {
$scope.nukes = data;
});*/
$scope.nukes = nukeService.getNukes();
}
If I use the $http.get from the controller the data populates fine, however, if I try to call the data from the service, I get nothing. I understand that the query is asynchronous but I am having a hard time understanding how to populate the $scope variable once the data is returned. I could use $rootscope to broadcast an event and listen for it in the controller but this does not seem like the correct way to accomplish this. I would really appreciate any advice on how to do this the correct way.
I think this should solve your problem
app.factory('nukeService', function($rootScope, $http) {
var nukeService = {};
nukeService.data = {};
//Gets the list of nuclear weapons
nukeService.getNukes = function() {
$http.get('nukes/nukes.json')
.success(function(data) {
nukeService.data.nukes = data;
});
return nukeService.data;
};
return nukeService;
});
function NavigationCtrl($scope, $http, nukeService){
$scope.data = nukeService.getNukes();
//then refer to nukes list as `data.nukes`
}
This is a problem with object reference.
when you calls nukeService.getNukes() you are getting a reference to a object a then your variable $scope.nukes refers that memory location.
After the remote server call when you set nukeService.nukes = data; you are not changing the object a instead you are changing nukeService.nukes from referencing object a to object b. But your $scope.nukes does not know about this reassignment and it still points to object a.
My solution in this case is to pass a object a with property data and then only change the data property instead of changing reference to a
This should be as follows. As mentioned by NickWiggill's comment, undefined will be assigned to nukeService.data if we do not return promise.
app.factory('nukeService', function($rootScope, $http) {
var nukeService = {};
//Gets the list of nuclear weapons
nukeService.getNukes = function() {
return $http.get('nukes/nukes.json');
};
return nukeService;
});
function NavigationCtrl($scope, $http, nukeService){
nukeService.getNukes().then(function(response){
$scope.data = response.data;
});
}
What I do is that I expose the data straight from the service, and have a method which initializes this data. What is wrong with this?
Service:
app.factory('nukeService', function($scope, $http) {
var data = {};
data.nukes = [];
//Gets the list of nuclear weapons
var getNukes = function() {
$http.get('nukes/nukes.json').success(function(data) {
data.nukes = data;
});
};
// Fill the list with actual nukes, async why not.
getNukes();
return {
data : data
// expose more functions or data if you want
};
});
Controller:
function NavigationCtrl($scope, nukeService){
$scope.data = nukeService.data;
//then refer to nukes list as `$scope.data.nukes`
}
Related
Before i state my question, i may be completely wrong with how i am using factories in my use case. Happy to know better alternatives.
So, I have two Factories.
Factory1 takes care of hitting the server, getting and storing that data.
Factory2 does not make any REST Calls. But stores a set of data that would be useful while making REST calls.
Simple question would be, why not Factory1 store that data instead of Factory2. The motive behind Factory2 is that this data would be used by lot of other factories/controllers. So, to remove redundancy this looks a better option.
For now,
When a REST call is to be made from Factory1, i can simply do Factory2.getData and then use it. But my use-case is bit more complex. I need Factory1 to fire calls whenever there is a change in Factory2.getData(). We would use $scope normally in case of controllers etc. But how to achieve the same thing here ?
Basically, How to do we know in Factory1 when there is a change in Factory2's data ?
Code:
servicesModule.factory('Factory2', function() {
var fObj = {},
filters;
fObj.setFilters = function(fs) {
filters = fs;
};
fObj.getFilters = function() {
return filters;
};
return fObj;
});
servicesModule.factory('Factory1', ['$http', '$q', 'Factory2', function($http, $q, Factory2) {
var fObj = {},
data;
fObj.fetchData = function(params) {
var filters = Factory2.getFilters();
// REST call here and set data to result
}
fObj.getData = function(){
return data;
}
}]);
So, a bit of digging pointed in the direction of $rootScope.
servicesModule.factory('Factory2', ['$rootScope', function($rootScope) {
var fObj = {},
filters;
fObj.setFilters = function(fs) {
filters = fs;
$rootScope.$emit("FiltersUpdated", fs);
};
fObj.getFilters = function() {
return filters;
};
return fObj;
}]);
servicesModule.factory('Factory1', ['$http', '$q', 'Factory2', '$rootScope', function($http, $q, Factory2, $rootScope) {
var fObj = {},
data;
$rootScope.$on('FiltersUpdated', function(event, data) {
fObj.fetchData();
})
fObj.fetchData = function(params) {
var filters = Factory2.getFilters();
// REST call here and set data to result
}
fObj.getData = function(){
return data;
}
}]);
I would still love to hear others' opinions.
I have my data factory below, which returns a promise
.factory('DataService', function($http, $document, CreatorService) {
promise = null;
jsonData = null;
return {
getJsonDataFromApi: function () {
promise = $http.get('/test');
return promise;
}
}
getJsonData: function () {
return jsonData;
},
setJsonData: function (data) {
jsonData = data;
}
}
My controller makes use of this service as follows
.controller('MainCtrl', function ($scope, $http, $uibModal, DataService, StyleService, CreatorService) {
$scope.dataService = DataService;
$scope.dataService.getJsonDataFromApi().success(function (data) {
$scope.dataService.setJsonData(data['content']);
$scope.jsonData = $scope.dataService.getJsonData();
As you can see, I'm trying to bind $scope.jsonData to the data service jsonData object via $scope.jsonData = $scope.dataService.getJsonData(); but this doesn't seem to work.
If I update the value of $scope.jsonData(), the value returned by $scope.dataService.getJsonData() doesn't change.
For example, if I do
$scope.jsonData = {};
console.log($scope.jsonData);
console.log($scope.dataService.getJsonData());
The output is
{}
Object{...}
I would have expected them to be the same. I can't seem to get my service object to update if I change my controller scope object, nor can I get my controller scope object to update if I make a change to the service object. I want to avoid using watches.
Two way data binding is for binding with your $scope and views. Not for controller and services. So if you really need something like this, then you need to $watch and set the data in service everytime it changes.
I have two controllers: Controller1 and Controller2
In Controller1's $scope, I have set up all my values I need. Using the data in $scope, I'm trying to run certain functions and pass the return values to Controller2.
I was thinking about making a factory to pass variable from Controller1 to Controller2. However, I realized all input values I need lives in Controller 1. I wonder whether factory can persist the data when it runs in Controller1 and return that data when it runs again in Controller2.
Thanks
Factory is a singleton so it can be used to share data among different controllers or directives. Take a look at the fiddler here. I have created a factory 'sharedContext' which can be used to share any key-value pair across controllers using different $scope.
Factory
myApp.factory("sharedContext", function() {
var context = [];
var addData = function(key, value) {
var data = {
key: key,
value: value
};
context.push(data);
}
var getData = function(key) {
var data = _.find(context, {
key: key
});
return data;
}
return {
addData: addData,
getData: getData
}
});
From the controller that needs to share the object can call the 'addData' method of the factory to store the data under a key. The other controllers/directives which are interested in accessing the shared data can do so by calling the 'getData' method and passing the correct key.
Controller (Sharing Data)
function MyCtrl_1($scope, sharedContext) {
$scope.input_1 = 5;
$scope.input_2 = 15;
$scope.add = function() {
$scope.result = $scope.input_1 + $scope.input_2;
sharedContext.addData("Result", $scope.result)
}
}
Controller (accessing shared data)
function MyCtrl_2($scope, sharedContext) {
$scope.getData = function() {
$scope.result = sharedContext.getData("Result").value;
}
}
The only assumption here is that both the controllers need to use the exact key to share the data. To streamline the process you can use a constant provider to share the keys. Also note that I have used underscore.js to look for the key in the shared context dictionary.
This is the simplest solution that you can come up with. As you can see the factory is a simple object and because of that construct it's passed by reference not by value that means in both controller dataFactory is the same
http://plnkr.co/edit/eB4g4SZyfcJrCQzqIieD?p=preview
var app = angular.module('plunker', []);
app.controller('ControllerOne', function (dataFactory) {
this.formFields = dataFactory
});
app.controller('ControllerTwo', function (dataFactory) {
this.formData = dataFactory
});
app.factory('dataFactory', function () {
return {};
})
edit
app.factory('dataFactory', function () {
var factory = {
method1: function (arg) {
console.log('method1: ', arg)
factory.method2('called from method1')
},
method2: function (arg) {
console.log('method2: ', arg)
}
}
return factory;
})
I have one service for handling data, and one for logic.
app.service('DataService', function() {
this.stuff = [false];
this.setStuff = function(s){
this.stuff = angular.copy(s);
}
});
The data service has a set function and a data property.
app.service('LogicService', function(DataService, $http) {
DataService.setStuff(["apple", "banana"]);
$http.get("./data.json").then(function(res){
DataService.setStuff(res.data.stuff);
});
});
I am assigning a property of the data service to the controller for binding to the DOM.
app.controller('MainCtrl', function($scope, DataService, LogicService ) {
$scope.message = "Hello, World!";
$scope.stuff = DataService.stuff;
//This is the only way I could get it to work, but isn't this JANKY?
//$scope.$watch(
// function(){
// return DataService.stuff
// },
// function(n,o){
// $scope.stuff = n;
// })
})
If I 'seed' the data service when the logic service is instantiated, and then later update it following an $http call, the DOM reflects the 'seeded' or initial value, but does not update.
Is there something fundamental I am missing in my understanding of the digest loop?
If I add a $watch function in my controller, all is well, but this seems yucky.
//FIXED//
#scott-schwalbe 's method of using Object.asign() works nicely, preserves my original structure, and is one line.
this.setStuff = function(s){
Object.assign(this.stuff, s);
}
Working Plunker
(sorry for titlegore)
If your data property is an object and is binded to the scope, then the scope will update whenever the object changes as long as you don't dereference it (eg data = x). Are you reassigning data object on the $http call?
An alternative to your current code to keep the reference using Object.assign
app.service('DataService', function() {
this.stuff = [false];
this.setStuff = function(s){
Object.assign(this.stuff, s);
}
});
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, DataService) {
$scope.message = "Hello, World!";
//Get stuff data from your service, this way you stuff lives in your service
//And can be accessed everywhere in your app.
//It also makes your controller thin. Which is the top priority
$scope.stuff = DataService.getStuff();
//Or async
DataService.getStuffAsync()
.then(function(val){
$scope.asycStuff = val;
});
this.clickFromAButton = function(){
DataService.setStuff(["apple", "banana"]);
};
});
app.service('DataService', function() {
this.stuff = [false];
this.asyncStuff;
this.setStuff = function(s){
this.stuff = angular.copy(s);
};
this.getStuff = function(){
return this.stuff;
};
this.getStuffAsync = function(){
//If i already fetched the data from $http, get it from the service.
return this.asyncStuff || $http.get("./data.json").then(function(res){
//When i fetch it for the first time I set the data in my service
this.asyncStuff = res.data;
//and I return the data
return res.data;
});
};
});
This is a good 'pattern' to follow ;)
Instead of putting "stuff" on scope. Put your DataService object on scope.
app.controller('MainCtrl', function($scope, DataService, LogicService ) {
$scope.message = "Hello, World!";
$scope.DataService = DataService;
//$scope.stuff = DataService.stuff;
HTML
<body ng-controller="MainCtrl">
{{DataService.stuff}}
</body>
The $interpolate service will automatically places a $watch on DataService.stuff. Thus there is no need to do it inside your controller.
The DEMO on PLNKR.
Hope, my question itself, conveys what I am look for.
Will put the words in detail
1. Created the Module.
var ang = angular.module('myApp', []);
I have a controller called controller1, and includes the 'campaign' factory.
//controllerone.js
ang.controller('controller1', function(campaign){
$scope.campaigns = new campaign();
//Here the whole campaign object is displayed with data, refer the Image 1 attached
console.log($scope.campaigns);
});
ang.factory('campaign', function($http){
var campaign = function(){
this.timePeriodList = buildTimePeriodList();
...
...
this.campaignList = [];
};
Campaigns.prototype.fetchCampaigns = function() {
//Some service call to load the data in this.campaignList
};
});
Now trying to call the same campaign factory in the second controller, getting only the object structure, not getting the data.
//controlertwo.js
ang.controller('controller2', function(campaign){
$scope.campaigns = new campaign();
//Here only the campaign object structure is displayed, but no data for campaignList, ref image 2 attached
console.log($scope.campaigns);
});
Since, factory service is a singleton object, I was expecting for same result as I got in controllerone.js,
Image 1:
Image 2:
In angular factory I would suggest a different approach.Try not to attach anything to the Prototype of a object. Instead you can create an object in the scope of your angular factory, attach what you want to it and return it. For example:
ang.factory('campaign', function ($http) {
var campaign = {};
campaign.method1 = function () {
//..
}
campaign.campaignList = [];
//...
campaign.fetchCampaigns = function () {
//Some service call to load the data in this.campaignList
};
});
//Than in your controllers if campaign is injected you can use it that way:
ang.controller('controller2', function (campaign) {
campaign.fetchCampaigns();// This will fill the list and it will remain filled when other controllers use this factory...
console.log(compaign.campaignList);
});
Anything you dont want to be exposed out of the factory simply do not attach it to the campaign object.
Create campaign in a service. Eg-campaignService
Updated code---
var ang = angular.module('myApp', []);
ang.service('campaignService', function($scope){
var campaign = function(){
this.timePeriodList = buildTimePeriodList();
...
...
this.campaignList = [];
}
return campaign;
});
ang.controller("controller1", ["campaignService",
function($scope, $rootScope, campaignService){
$scope.campaigns=campaignService.campaign();
}
]);
ang.controller("controller2", ["campaignService",
function($scope, $rootScope, campaignService){
$scope.campaigns=campaignService.campaign();
console.log($scope.campaigns);
}
]);