I have an http-method that gets some data from a google spreadsheet. I want to add this to the $scope so I can output it in the DOM. Later I might make a timed loop of this so that the $scope get's updated every 5 seconds or so.
I currently run the code in app.run:
angular.module('spreadsheet2angular', []).
run(function($http){
$http({method: 'GET', url: 'http://cors.io/spreadsheets.google.com/feeds/cells/0Aq_23rNPzvODdFlBOFRYWlQwUFBtcXlGamhQeU9Canc/od6/public/values?alt=json'}).
success(function(data, status, headers, config) {
var entries = data.feed.entry;
var phraces = [];
entries.forEach(function(entry){
var cell = entry.gs$cell;
if(!phraces[cell.row]){
phraces[cell.row] = {};
}
if(cell.col == 1)
{
phraces[cell.row].name = cell.$t;
}
else if(cell.col == 2)
{
phraces[cell.row].value = cell.$t;
}
});
phraces.forEach(function(phrace){
console.log(phrace);
});
}).
error(function(data, status, headers, config) {
console.log('error');
});
});
I'm new to angular, is this the best place to run it? I would like to run it as something that is easily reusable in different projects.
I think from what you've explained, a service would be perfect. Build it out then inject it in your controller. You can then call/use that service object whenever you would like.
I would use service/factory that returns promise. So we call async service method, get back promise and parse response into controller.
If you think to use the same call in the future, you can write generic method.
By the same way, if you are going to parse response by the same way in the future, the part of logic I would put into the service as well and wrap with $q . So the response still will be promise.
And this is an example I use that might help you to understand what I'm meaning:
app.service('apiService', ['$http', '$q', '$rootScope',
function($http, $q, $rootScope) {
var request = function(method, data) {
var deferred = $q.defer();
var configHttp = {
method: 'POST',
url: config.api + '/' + method
};
if (data !== undefined) {
configHttp.data = data;
}
$http(configHttp).success(function(data, status, headers) {
if (data.error === undefined) {
deferred.resolve(data);
} else {
deferred.reject(data);
}
}).error(function(data, status, headers) {
deferred.reject(data);
});
return deferred.promise;
}
return {
getItem: function() {
return request('get_item');
},
getItemByParams: function(id) {
return request('get_item_by_params', {id: id});
}
};
}
]);
Related
I pass two dates to the post method thro controller.The service responses back with some data based on the given input. Im using $scope.onGetData to get the data from post method, inorder to display the final result but it is not going inside the $scope.onGetData. So the question is how to fetch the response data from the service and use it inside a controller, so that I can make use of it in my view.
Controller:
$scope.computationList;
$scope.onViewLoaded = function () {
computationManagementService.getComputation($scope.onGetData);
}
$scope.onGetData = function (data,response,error) {
$scope.computationList = data;
}
$scope.calculateInput=function(start,end,htmlValidation)
{
var date={'startDate':start , 'endDate':end};
if(htmlValidation){
computationManagementService.getComputation(date,function(err,response){
console.log("pass thro controller");
});
}else{
console.log("Validation Error");
}
}
});
Service:
myApp.factory('computationManagementService', function($http, settings){
var ComputationServiceFactoryObj = {};
var _getComputation= function(date,callback){
$http({
method:'POST',
url: 'localhost:/8091/date/computation',
data: date
}).success(function(data,response,config){
callback(response);
console.log(data); // data
}).error(function (data, status, error, headers, config){
if(callback) {
callback(error);
console.log(error);
}
});
}
ComputationServiceFactoryObj.getComputation= _getComputation;
return ComputationServiceFactoryObj;
});
If you are trying to use the post method's data to the view then you can try this method And it worked for me but not sure if it is correct way of using the service.
Service:
myApp.factory('computationManagementService',
function($http, $rootScope, settings){
var ComputationServiceFactoryObj = {};
var _getComputation=function(callback){
var computationData=$rootScope.finalResult;
if(callback != null){
callback(computationData);
}
}
var _postComputation= function(date,callback){
$http({
method:'POST',
url: 'localhost:/8091/date/computation',
data: date
}).success(function(data){
callback(data);
$rootScope.finalResult=data;
console.log(data); // data
}).error(function (data, status, error, headers, config){
if(callback) {
callback(error);
console.log(error);
}
});
}
ComputationServiceFactoryObj.getComputation= _getComputation;
ComputationServiceFactoryObj.postComputation= _postComputation;
return ComputationServiceFactoryObj;
});
Some good practices:
using ngResource is always preferable to the raw $http service, except for rare cases when you need some complex configuration that ngResource can't handle (I can't think of such, though). Why? It forces you yo use promises.
return promises from your service methods instead of passing callbacks. Using callbacks will force you to call $digest on your scope so that bingings are re-evaluated, which goes against the way angular works in general and may have negative performance impact as well.
In your case I'd modify the _getComputation method to simply return a promise:
var _getComputation = function(date) {
return $http({
method:'POST',
url: 'localhost:/8091/date/computation',
data: date
});
};
In your controller:
computationManagementService.getComputation(date)
.then(function(response) {
console.log(response);
$scope.someValue = response.someValue;
}, function(error) {
console.error(error);
});
I'd rather avoid injecting $scope in controllers, and use the ngController='MyController' as 'MyCtrl' syntax instead and assign values that should be accessible by views to the controller instance.
It is better not to use .success and .error methods in your service as they are not chainable, Use .then format instead. .success/error methods are deprecated in the latest Angular version 1.6.
Here is the Deprecation notice from Angular documentaion.
In your service :
var _getComputation= function(date,callback){
return $http({
method:'POST',
url: 'localhost:/8091/date/computation',
data: date
}).success(function(data,response,config){
callback(undefined, response);
console.log(data); // data
}).error(function (data, status, error, headers, config){
if(callback) {
callback(error);
console.log(error);
}
});
}
In your Controller :
computationManagementService.getComputation(date,function(err,response){
console.log("pass thro controller");
console.log(response);
}
As soon as Html page gets loaded, it calls SuperCategoryController, where i am assigning supercategories to $scope variable.
$scope.SuperCategories = SuperCategoryService.GetSuperCategories();
But as this controller is depends on service, which in turn calls the http request. so at the time pf assignment http request is not completed. so $scope.SuperCategories is getting assiged to undefined.
sampleApp.service('SuperCategoryService', ['$http', function ($http){
var URL = 'http://localhost/cgi-bin/superCategory.pl';
var SuperCategories;
$http({
method: 'POST',
url: URL,
data: "action=GET",
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}).
success(function (data) {
alert (data);
if (data != null || data != 'undefined') {
SuperCategories = data;
}
})
.error(function (error) {
alert (error.message);
//$scope.status = 'Unable to retrieve super categories' + error.message;
});
//simply returns the SuperCategories list
this.GetSuperCategories = function () {
//alert (SuperCategories);
return SuperCategories;
}
}]);
sampleApp.controller('SuperCategoryController', ['$scope', 'SuperCategoryService', function ($scope, SuperCategoryService){
$scope.SuperCategories = SuperCategoryService.GetSuperCategories();
$scope.LoadSuperCategoryMapping = function()
{
alert ($scope.SelectedSuperCategory.id);
}
}]);
How to solve this problem in proper way.
I haven't tried this code myself but I would approach a solution using factory and a promise to make sure the data has been loaded. Something along these lines:
sampleApp.factory('SuperCategoryService', ['$http', function ($http){
return {
GetSuperCategories: function () {
var URL = 'http://localhost/cgi-bin/superCategory.pl';
return $http.get(URL);
}
}
}]);
sampleApp.controller('SuperCategoryController', ['$scope', 'SuperCategoryService', function ($scope, SuperCategoryService){
$scope.SuperCategories = function() {
SuperCategoryService.GetSuperCategories()
.then(function(d) {
if (d.data != undefined) {
// Your data should be loaded here
console.log(d.data);
$scope.SuperCategories = d.data;
}
})
.error(function(data, status) {
// Errors here
});
}
}]);
On my app, I need to recover data (json) by making multiples validations using http requests before all my app starts. So my problem is that I'm using angular.run() to make all the http requests and resolving all of the validations with promises.
The problem is, not all of my promises are executed before my app is started.
part of my code is:
appModule.run(configRun);
configRun.$inject = [
'$http', '$rootScope', 'gettextCatalog', 'ipLoadDataService',
'webStorageService', 'ipDataSetParserService'];
function configRun($http, $rootScope, gettextCatalog, ipLoadDataSrv, webStrSrv, dataSetParser) {
webStrSrv.clear();
ipLoadDataSrv.getHeadDataSet2()
.then(function (responseHead) {
if (ipLoadDataSrv.updatedDataSet2(responseHead.headers["last-modified"])) {
//save into localstorage
webStrSrv.clear();
webStrSrv.setItem("last-modified", { date: responseHead.headers["last-modified"] });
ipLoadDataSrv.getDataSet2()
.then(function (responseData) {
$rootScope.cabecera = responseData;
})
}
})
}
// LoadDataService
appModule.factory('ipLoadDataService', loadDataService);
loadDataService.$inject = ['$http',
'$q',
'webStorageService',
'myPrjEnvironment',
'ipDataSetParserService'];
function loadDataService($http, $q, webStoreService, myPrj, dataSetParser) {
var eventMap = [];
var ip_loadDataService = {
getHeadDataSet2: getHeadDataSet2,
requestDataSet: requestDataSet,
updatedDataSet2: updatedDataSet2,
getDataSet2: getDataSet2
};
return ip_loadDataService;
function getHeadDataSet2() {
/*HEAD*/
var deferred = $q.defer();
$http.head(myPrj.URL_DATA)
.success(function (data, status, headers, config) {
var response = [];
response.data = data;
response.headers = headers();
deferred.resolve(response);
//return deferred.promise;
}).error(function (data, status) {
deferred.reject(data);
});
return deferred.promise;
}
function getDataSet2() {
return xhr('get', [myPrj.URL_DATA]);
}
function updatedDataSet2(last_date_modified) {
//var self = this;
var dateOnWebStore = webStoreService.getItem("last-modified");
if (dateOnWebStore === null || Date.parse(dateOnWebStore.date) < Date.parse(last_date_modified))
return true;
return false;
}
function xhr(type, config) {
if (!config && angular.isArray(type)) {
config = type;
type = 'get';
}
var deferred = $q.defer();
$http[type].apply($http, config)
.success(function (data, status, headers, config) {
var response = [];
response.data = data;
response.headers = headers();
deferred.resolve(response);
})
.error(function (error) {
deferred.reject(error);
});
return deferred.promise;
}
}
Answering the question in your second post, maybe you better edit your original post with the new issue you encountered.
If what you are looking for is a way to activate a state (home.myPrjMain in your case) you can do this in various ways:
Using JS - use $state.go(). See - $State documentation
Using directive - use the ui-sref directive with the name of the required state. See - ui-sref documentation
Using regular html href (Navigate to url) - with the full address of the state you need. In your case, "/main".
I hope this was helpful
Have the UI start in an initial loading state then use ui-router to wait for the various pieces to resolve before going to the initial state.
Here is a fiddle showing how it works. Fiddle
I did two parts, one with a single fake service call using timeout and a second with a chained set of calls,.
this.slowServiceCall = function(input, delay) {
var deferred = $q.defer();
var workFinished = function () {
deferred.resolve(input);
};
$timeout(workFinished, delay);
return deferred.promise;
};
this.slowChainedServiceCall = function(input, delay) {
var deferred = $q.defer();
var workFinished = function () {
deferred.resolve(input);
};
$timeout(workFinished, delay);
var promiseChain = deferred.promise.then(function(result) {
var deferred2 = $q.defer();
$timeout(function(){
deferred2.resolve(result + ' Second Piece');
},100);
return deferred2.promise;
});
return promiseChain;
};
I'm trying to write an Angular service and it seems like there is something missing. My problem is its not returning any value to my Angular controller
getPrepTimes() method is not returning the http data
But when I check the network (via Chrome dev tools) it will correctly call the external api and return a json object as a response
#my service
'use strict';
angular.module('recipeapp')
.service('prepTimeService',['$http', function($http){
this.prepTime = getPrepTimes();
function getPrepTimes(){
$http({
url: '/prep_times/index.json',
method: 'GET'
})
.success(function (data, status, header, config){
return data;
});
};
}
]);
#controller
'use strict';
angular.module('recipeapp')
.controller('recipeCtrl', ['$scope', 'prepTimeService', function($scope, prepTimeService){
$scope.prep_time = prepTimeService.prepTime;
}]);
When I checked the method getPrepTimes() with returning a string it works. What could be missing here?
A couple things are wrong with the above. You assign this.prepTime to getPrepTimes(). The () there will invoke getPrepTimes immediately, and not when you actually call it! You also need to utilize callbacks to get your data back and use it:
angular.module('recipeapp').service('prepTimeService',['$http', function($http){
this.prepTime = getPrepTimes;
function getPrepTimes(callback) {
$http({
url: '/prep_times/index.json',
method: 'GET'
}).success(function (data, status, header, config){
callback(data);
});
};
}]);
And now use it like so:
prepTimeService.prepTime(function(data) {
$scope.prep_time = data;
});
Calls to the $http service are async, which means you need to return a promise (and not a value):
this.prepTime = function() {
return $http({
url: '/prep_times/index.json',
method: 'GET'
});
};
And on the controller:
angular.module('recipeapp')
.controller('recipeCtrl', ['$scope', 'prepTimeService', function($scope, prepTimeService){
$scope.prep_time = prepTimeService.prepTime()
.success(function (data, status, header, config){
$scope.someVar = data;
});
}]);
Wrap answer with promise:
var self = this;
var deferred = $q.defer();
self.getPrepTimes = function() {
$http({
url: '/prep_times/index.json',
method: 'GET'
})
.success(function(data, status, headers, config) {
if (data.error === undefined) {
deferred.resolve(data);
} else {
if (data.error !== undefined) {
} else {
deferred.reject(data);
}
}
}).error(function(data, status, headers, config) {
deferred.reject(data);
});
return deferred.promise;
};
In controller call it:
prepTimeService.getPrepTimes().then(function(result) {
$scope.prep_time = result;
},
function(error) {
// show alert
});
I'm using a service in order to share data between controllers. However, the service is returning a promise with cached data even when making new requests. Depending on where the defer instance is created either live data is returned but two-way binding breaks or the two-way binding works but cached data is returned.
How can one prevent the return of a promise with cached data and keep two-way binding?
I've put up a plunker to illustrate the case: http://plnkr.co/edit/SyBvUu?p=preview and for sake of completeness, here is the troublemaking service:
app.service('myService', function($http, $q) {
// When instancing deferred here two way binding works but cached data is returned
var deferred = $q.defer();
this.get = function(userId) {
// When instancing deferred here two way binding breaks but live data is returned
//var deferred = $q.defer();
console.log('Fetch data again using id ', userId);
var url = userId + '.json';
$http.get(url, {timeout: 30000, cache: false})
.success(function(data, status, headers, config) {
deferred.resolve(data, status, headers, config);
})
.error(function(data, status, headers, config) {
deferred.reject(data, status, headers, config);
});
return deferred.promise;
};
});
UPDATE: The problem wasn't that data was cached, it was that I hadn't understood how a data was to be shared and that the shared data can not be a primitive. See my own answer below.
Since $http returns a deferred object what you are doing here is actually overkill. When I changed your service to the following it seems to work fine.
Plunker
app.service('myService', function($http, $q) {
this.get = function(userId) {
console.log('Fetch data again using id ', userId);
var url = userId + '.json';
return $http.get(url, {timeout: 30000, cache: false});
};
});
Edit
To get your controller SecondCtrl to update, the easiest thing to do, while keeping the structure of your code the same, is to broadcast the new data in an event defined in FirstCtrl using $rootScope.$broadcast and capture the broadcasted event in your other controller using $scope.$on. I've updated the Plunker and now your data is in sync.
Modified loadUserFromMyService function in FirstCtrl:
$scope.loadUserFromMyService = function(userId) {
var promise = myService.get(userId);
promise.then(
function(data) {
console.log('UserData', data);
$scope.data = data;
$rootScope.$broadcast('newData', data);
},
function(reason) { console.log('Error: ' + reason); }
);
};
Added in SecondCtrl:
$scope.$on('newData', function (evt, args) {
console.log('dataChanged', args);
$scope.data = args;
});
I came up with simplified solution to share data with the help of Luke Kende. Here is a plunk: http://plnkr.co/edit/JPg1XE?p=preview. See code below.
One important thing is that the shared object isn't a primitive. When I tried different solutions I started with declaring the shared object and assign it null, which is a no-no. Using an empty object makes it work.
var app = angular.module('plunker', []);
// Service
app.service('myService', function($http, $q) {
//object that will be shared between controllers
var serviceData = {
items: []
};
return {
data: serviceData, //pass through reference to object - do not use primitives else data won't update
get: function(url, overwrite) {
if (serviceData.items.length === 0 || overwrite){
$http.get(url, {timeout: 30000})
.success(function(data, status, headers, config) {
//could extend instead of ovewritting
serviceData.items = data;
})
.error(function(data, status, headers, config) {
serviceData.items = {status: status};
});
}
return serviceData;
},
empty: function(){
serviceData.items = [];
},
more: function(){
//do some other operations on the data
}
};
});
// Controller 1
app.controller('FirstCtrl', function( myService,$scope) {
//myService.data is not initialized from server yet
//this way don't have to always use .then() statements
$scope.data = myService.data;
$scope.getTest = function(id){
myService.get('test' + id + '.json',true);
};
$scope.addItem = function() {
$scope.data.items.push({'title': 'Test ' + $scope.data.items.length});
};
$scope.delItem = function() {
$scope.data.items.splice(0,1);
};
});
// Controller 2
app.controller('SecondCtrl', function( myService,$scope) {
//just attach myService.data and go
//calling myService.get() results in same thing
$scope.data = myService.data;
//update the the data from second url -
$scope.getTest = function(id){
myService.get('test' + id + '.json',true);
};
$scope.empty = function(){
myService.empty();
};
});