Issues with two way data binding between service and controller - javascript

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.

Related

Accessing $scope data from view to factory in AngularJs

How can I access $scope data from view to my factory in angularjs? I can access $scope.items from my controller, but when I need to use it in my factory to use the data and generate a pdf I cannot access it.
angular.module('myApp', [])
.controller('myCtrl', function($scope, $http, testFactory) {
$scope.link = "http://localhost:3450/loading.html";
testFactory.all().then(
function(res){
$scope.link = res;
},
function(err){
console.log(err);
}
);
})
.factory('testFactory', function($q){
var pdfInfo = {
content: [
//data should be here...
]
};
var link = {};
function _all(){
var d = $q.defer();
pdfMake.createPdf(pdfInfo).getDataUrl(function(outputDoc){
d.resolve(outputDoc);
});
return d.promise;
}
link.all = _all;
return link;
});
I used factory when I click the generate button from my view, it will wait until the pdf is generated. Coz when I did not do it this way before, I need to click the button twice just to get the pdf generated.
You can just pass the data to your factory as a
function parameter.
angular.module('myApp', [])
.controller('myCtrl', function($scope, $http, testFactory) {
var pdfInfo = {
content: $scope.items
};
$scope.link = "http://localhost:3450/loading.html";
testFactory.all(pdfInfo).then(
function(res) {
$scope.link = res;
},
function(err) {
console.log(err);
}
);
})
.factory('testFactory', function($q) {
var link = {};
function _all(pdfInfo) {
var d = $q.defer();
pdfMake.createPdf(pdfInfo).getDataUrl(function(outputDoc) {
d.resolve(outputDoc);
});
return d.promise;
}
link.all = _all;
return link;
});
I did it. I forgot to send the $scope.items to my factory. So what i did is I added testFactory.all($scope.items) in my controller instead of just plain testFactory.all().
Then in my factory,
I used function _all(value), so I can used the values passed by the views through controller. I am not sure if this is the proper way, but it works. Please suggest good practice if you have.
It is a bad practice to move around $scope to other services, as they may change it and effect your controller logic. It will make a coupling between controllers to other services.
If your factory requires data from the controller, it is better to just pass those parameters to the factory's function.
EDIT: I see you managed to do that, and yes - passing $scope.items is the preferred way (and not, for example, passing $scope).

Passing variables between controllers using AngularJS

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;
})

Getting results twice when calling service with controller

I am building a simple controller, which also includes a service. This service returns a json. Now, my problem is, when i call the request function via the controller and print out the results, I get all objects twice.
So this is my code right now:
angular.module('clientApp')
.factory('RequestService', ['$http', function($http){
function getData() {
return $http.get('../../mockdata/data.json')
}
return {
getData: getData
};
}])
.controller('MainCtrl', ['RequestService', function(RequestService) {
var data = [];
RequestService.getData()
.success(function(allData) {
data = allData[0];
console.log(allData)
})
}]);
json: http://pastebin.com/dTcNDQeA
I guess, the problem must be somewhere around the .success, but I can`t find out why specifically.
My Solution: Finally it worked fine for me when I wrapped a function around the the service call in the controller. Still figuring out, why.
Try not returning anything from your service.
.service('RequestService', ['$http', function($http){
this.getData = function() {
return $http.get('../../mockdata/data.json')
}
}])
// controller
RequestService.getData().success(function(data){
$scope.data = data;
})
To only call the service once you can see this example
Try this
angular.module('clientApp')
.service('RequestService', ['$http', function($http){
var getData = function (handler) {
$http.get('../../mockdata/data.json').success(handler)
}
return {
getData: getData
};
}])
.controller('MainCtrl', ['RequestService', function(RequestService) {
var data = [];
RequestService.getData(function(allData) {
data = allData[0];
console.log(allData)
});
}]);
You have defined your service as if it were a factory.
Try, either:
change the .service to a .factory
or:
do this.getData = function () { ... }
(as #user2954587 says)
Ideally it should return single object. May be JSON you are getting from backend has problem

Share data between controllers in AngularJS

I use the following factory to fetch data from API and store it to local variable called apiData.
app.factory('MyFactory', function($resource, $q) {
var apiData = [];
var service = {};
var resource = $resource('http://example.com/api/sampledata/');
service.getApiData=function() {
var itemsDefer=$q.defer();
if(apiData.length >0) {
itemsDefer.resolve(apiData);
}
else
{
resource.query({},function(data) {
apiData=data;
itemsDefer.resolve(data)
});
}
return itemsDefer.promise;
};
service.add=function(newItem){
if(apiData.length > 0){
apiData.push(newItem);
}
};
service.remove=function(itemToRemove){
if(apiData.length > 0){
apiData.splice(apiData.indexOf(itemToRemove), 1);
}
};
return service;
});
I inject the apiData into my controllers the following way:
$routeProvider
.when('/myview', {
templateUrl: 'views/myview.html',
controller: 'MyController',
resolve: {
queryset: function(MyFactory){
return MyFactory.getApiData();
}
}
})
app.controller('MyController', ['$scope', 'queryset',
function ($scope, queryset) {
$scope.queryset = queryset;
}
]);
Is it a good way to share a promise between different controllers or I better to use local storage or even cacheFactory?
How can I rewrite MyFactory add() and remove() methods so that I can keep my shared variable in a current state (no API update/create/delete calls needed)?
Thank you!
You should use $resource to get $promise eg: resource.query().$promise instead of $q.defer() for cleaner code. Otherwise you are good to go. You could use $cacheFactory, but you can also use local var.
Isn't your shared variable in a current state with current code?
I recommend you take a look at ui-router and its nested and abstract views, where resolved values are available to child controllers.

AngularJS Load Data from Service

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`
}

Categories