I'm trying to use AJAX to bring in some data and then modify it before sending the result to a controller. I had this all working in the controller but I was told that it's better to do it in a service.
app.factory('GameweekJsonFactory', ['$http',function($http){
var promise;
var service = {
async: function() {
if ( !promise ) {
promise = $http.get('jsons/gameweeks.json').then(function(response){
return response.data
});
}
return promise;
}
}
return service;
}]);
Then in another factory I wanted to change the value of a variable dependent on the response of the AJAX call. (Say for example if it's gameweek 6 going by date, I want gwk to equal 6).
app.factory('GameweekFactory',['GameweekJsonFactory',function(GameweekJsonFactory) {
var gwk;
var obj = {
foo: function(){
GameweekJsonFactory.async().then(function(d){
//Code here to find out the current gameweek.
gwk = 6;
return gwk;
});
}
};
return gwk;
}]);
controller: function(GameweekFactory){
console.log('Controller Log '+GameweekFactory)
},
How do I then modify the original gwk variable in the factory's scope? I can't work it out and I've been googling for ages!
You are not defining the factory correctly. AngularJS factories are functions that return a value, and in this case it's an object with a method named foo.
Promises are chainable. The value returned by a success callback is passed into the next success callback. So you can manipulate the value (add gwk) and return that value.
app.factory('GameweekFactory',[
'GameweekJsonFactory',
function(GameweekJsonFactory) {
return {
foo: function(){
return GameweekJsonFactory.async().then(function(d){
//Code here to find out the current gameweek.
d.gwk = 6;
return d;
});
}
};
}]);
controller: function(GameweekFactory){
GameweekFactory.foo().then(function(d){
console.log('Controller Log '+d.gwk)
});
}
Related
I'm trying to get some data from a Service into a Controller and I keep get an undefined variable.
angular
.module("classes")
.service("MyService", function ($http) {
this.foo;
$http.get("/classes/all").then(function (response) {
this.fighters = response.data;
this.foo = this.fighters;
console.log(this.foo);
});
console.log(this.foo);
})
When I run this I get on the console, by this order, line 11 is undefined and then line 9 returns me the array.
And when in the controller I try to get the variable foo, it also says undefined.
$scope.fooFighters = MyService.foo;
The reason is because of asynchronous execution of your API call. I would suggest you to rewrite the code to use a factory that will return a promise object. No need to bring unnecessary variables.
angular.module("classes").factory("MyService", function($http) {
return {
fighters: function() {
return $http.get("/classes/all").then(function(response) {
return response.data;
});
}
}
})
And in your controller, you can get the value by injecting the service in the controller and then by referencing it like
MyService.fighters().then(function(data){
$scope.fooFighters = data;
});
because it will take some time to load the ajax/http request data after that line 9 will work. So if you want to work with ajax/http data then you should write code/function inside
$http.get("/classes/all").then(function (response) {
// do something
});
I had similar code to this working before and for some reason, this one chunk is not. It's a simple idea; call the function, it calls an $http-based factory to get data and return an array of that data. First is the factory:
app.factory('getSensors', ['data', function (data) {
return { get : function() {
data.get('sensors').then(function (results) {
var sensors = [];
for(var i = 0; i < results.sensors.length; i++) {
sensors.push({
'id' : Number(results.sensors[i][0]),
'name' : results.sensors[i][1],
'type' : results.sensors[i][2],
'device' : results.sensors[i][3],
'unit' : results.sensors[i][4],
'parent' : Number(results.sensors[i][5]),
'latitude' : Number(results.sensors[i][6]),
'longitude' : Number(results.sensors[i][7])
});
}
return sensors;
});
}
};
}]);
Which uses the following data service:
app.factory('data', ['$http', function ($http) {
var serviceBase = '/api/';
var obj = {};
obj.get = function (q) {
return $http.get(serviceBase + q).then(function (results) {
return results.data;
});
};
return obj;
}]);
And for the call to the factory, I've tried getSensors, getSensors.get, and getSensors.get(), even though I know some of those are wrong. I have also tried not wrapping this code in a function, using a function defined above the return, and returned the array in no object (how it is working in my other function, even though I don't think it is best practice, it's just simpler). I've even swapped the for loop for a forEach.
Basically, the returned value from the getSensors factory always happens before the $http call completes from my use of console debugging, even if the code is in the callback function and after that loop. I am at a loss as to how this is possible after spending 2 hours looking at it and rewriting this code. I maybe am simply missing something obvious or am violating some aspect of Angular.
The critical part you are missing is to return the promise created by the .then() in your getSensors.get() method:
app.factory('getSensors', ['data', function (data) {
return { get : function() {
return data.get('sensors').then(function (results) {
var sensors = [];
/* for loop (same as before) goes here */
return sensors;
});
}
};
}]);
Then to use it, just inject the getSensors service as a dependency, and call
getSensors.get().then(function (sensors) {
/* do something with sensors array */
});
I'm trying to expose the data obtained from the success method of a promise. In short, I don't know how to grab $scope.storedData. As it is right now, it is undefined.
genericService.js
myApp.factory('genericService', function($http){
return $http.jsonp('http://foo.com/bar.json')
.success(function(data){
return data;
})
.error(function(err){
return err;
});
});
genericController.js
myApp.controller('genericController', ['$scope','genericService',
function($scope, genericService){
genericService.success(function(data){
$scope.storeData(data);
});
$scope.storedData; // Undefined here.
$scope.storeData = function(whatever){
$scope.storedData = whatever;
}
console.log('data stored is: ', $scope.storedData); // Still undefined
}]);
How do I expose $scope.storedData to the scope outside of storeData() or genericService.success()?
Note: I don't want to use $watch. I want to overcome this scope issue fairly un-Angularly... because it should be possible.
There are 2 things I typically do:
I use models that define the expected response and will generally init my controller with an empty model.
I use a variable to track my state.
Here's an example of what my controller might look like:
myApp.controller('genericController', GenericController);
GenericController.$inject = [
'$scope',
'genericService'
];
function GenericController(
$scope,
genericService
) {
$scope.loadData = loadData;
$scope.storeData = storeData;
init();
///////////////////
function init() {
$scope.isLoaded = false;
$scope.storedData = {}; // if you use a model class, a new instance of this works best.
}
function loadData() {
genericService.success(function(data){
$scope.storeData(data);
$scope.isLoaded = true;
});
}
function storeData(whatever) {
$scope.storedData = whatever;
}
}
In one of my factories I need to set a variable when data is fetched (through $http) so I can access it in my controller (the intention is to display a spinner image until the data is loaded).
.factory('LoadData', function LoadData($http, $q){
return {
getMyData: function(){
return $http(
// implementation of the call
).then(
function(response){
var myData = response.data;
// this should be reference to the other method (getLoadStatus)
// where I want to change its value to "true"
// this doesn't work - "this" or "self" don't work either because we're in another function
LoadData.getLoadStatus.status = true;
}
);
},
// by calling the below method from my controller,
// I want to mark the completion of data fetching
getLoadStatus = function(){
var status = null;
return status;
}
}
}
I hope you got the idea - how could this be accomplished? Also, I'm open to any suggestions which are aimed towards a better approach (I want to pick up best practice whenever possible).
Status is essentially a private variable; use it as:
.factory('LoadData', function LoadData($http, $q){
var status = null; // ESSENTIALLY PRIVATE TO THE SERVICE
return {
getMyData: function(){
return $http(...).then(function(response){
...
status = true;
});
},
getLoadStatus = function(){
return status;
}
};
})
There are several ways.
Here's one which I prefer to use:
.factory('LoadData', function LoadData($http, $q){
var status = false;
var service = {
getMyData: getMyData,
status: status
};
return service;
function getMyData() {
return $http(
// implementation of the call
).then(
function(response){
var myData = response.data;
status = true;
}
);
}
}
This provides good encapsulation of your methods and gives you a clean interface to export. No need for the getter method if you don't want it.
Inspiration via John Papa's Angular style guide (found here).
You could simply store variable flag in closure:
.factory('LoadData', function LoadData($http, $q) {
var status = false;
return {
getMyData: function() {
status = false;
return $http(/* implementation of the call */).then(function(response) {
status = true;
return response.data;
});
},
getLoadStatus: function() {
return status;
}
}
});
Also if getMyData loads fresh data every time, it's important to reset status to false before each request.
I actually decide to use a promise in my controller after calling this service and when data is returned, I am simply making the status true. It seems that is best practice to have a promise returned when calling a service so I should be good.
Right now I have an Angular service named chartModelService with this method on it:
this.fetchChartModel = function() {
return $http.get(fullUrlPath);
};
And I have an Angular controller that consumes this data:
chartModelService.fetchChartModel().then(function(rawData) {
var chartData = chartModelService.translate(rawData);
$scope.populateChart(chartData);
});
I would like to perform the chartModelService.translate(data) within the chartModelService instead of the controller using the service. In other words, I would like to change to controller to the code below, where the chartData it receives has already been translated:
chartModelService.fetchChartModel().then(function(chartData) {
$scope.populateChart(chartData);
});
How can I change chartModelService.fetchChartModel() so that it performs translate() before returning the promised data?
Change this:
this.fetchChartModel = function() {
return $http.get(fullUrlPath);
};
To This:
this.fetchChartModel = function() {
var defObj = $q.defer();
var fetch = $http.get(fullUrlPath);
fetch.then(function(data){
defObj.resolve(chartModelService.translate(data));
});
return defObj.promise;
};
(with the appropriate DI on your service, of course)
This will init the data fetch and return a promise that contains your translated data when it has been fulfilled.
just move your code ;)
this.fetchChartModel = function() {
return $http.get(fullUrlPath)
.then(function(rawData) {
return $q.resolve(chartModelService.translate(rawData);)
});
};
that should do it just make sure you include $q service in your dependencies
For me didn't work $q.resolve, but you can use instead $q.when
this.fetchChartModel = function() {
return $http.get(fullUrlPath)
.then(function(rawData) {
return $q.when(chartModelService.translate(rawData));
});
};