I've a service like this:
angular.module('module')
.factory('UserList', function ($http) {
return {
getUserList: $http.get('/portal-services/NemesysPortalBackend/rest/user/all')
};
});
this constraint me to do
UserList.getUserList.then(function(res){$scope.data = res.data;})
in every controller where I need it.
Is there any way to "facade" it to simply have
$scope.data = UserList.getUserList();
Thanks
I'm making an assumption here that the user list doesn't change often, so this way you can cache it to a variable... otherwise you will need to make a call each time you expect the list changes (or reload the list using an interval?)
Fetching data before it's really needed is called "Eager loading"
angular.module('module').factory('UserList', function ($http, $q, $interval) { // import $q as well
var userList = []; // initialized as blank array
var refreshList = function(){
var deferred = $q.defer();
$http.get('/portal-services/NemesysPortalBackend/rest/user/all').then(
function(successResponse){
userList = successResponse.data;
deferred.resolve(successResponse);
},function(failureResponse){
// do something on error?
deferred.reject(failureResponse);
});
return deferred.promise;
}
refreshList(); // eager load, run right away
// i don't recommend this next line, there are better ways of doing this
$interval(refreshList(), 1000*60); // get new list every 60 seconds
return {
getUserList: function(){ return userList; }, // return user list only
refreshList: function(){ refreshList(); } // return promise which getting new list
};
});
Again, I don't recommend using $interval to reload the list, but instead call refreshList any time updates are made to the user list
Ex:
angular.module('module').controller('userCtrl', function(UserList) {
$scope.data = UserList.getUserList();
// once you change the user list, call a refresh
UserList.addUser().then(UserList.refreshList()).then(function(){
$scope.data = UserList.getUserList();
);
});
You cannot do this because JavaScript is single-threaded. This means, that when you use some call that is asynchronous, you cannot ever make it synchronous. There is no waiting(*) in Javascript. You cannot block your function call to wait for the results from the server.
Even if you tried as hard as this:
function getResult() {
var result;
UserList.getUserList.then(function(res) {
result = res.data; // this should break the loop below
});
while (!result) {}; // active waiting, wasting CPU cycles
return result;
}
...it wouldn't work because the callback function will not be executed until the currently running code (i.e., the infinite loop) finishes. An infinite loop like this would freeze the whole application forever.
(*) this doesn't mean that you cannot schedule functions to be called at some point later. Promises and closures help a lot with it.
Related
So been struggling a bit with getting promises to work properly, but after a lot of work, think I have gotten it. So now comes the question, can you create a self fulfilling promise, if you don't want to wait for the real thing...
Short pseudo version of what I want to do
var promise;
if (!factory.isDataLoaded()){
//The data is not loaded
var promise = factory.init();
} else {
//Data is all loaded
var promise = getSelfFullfilingPromise
}
//Some other code
promise.then(function({
//Do some stuff with the data from factory. which we know is loaded
})
Consider this option (taken straight from my app). I have a factory that loads up a couple of tables, especially one, it translates ids from one table to arrays of names, status and so forth from another table. Then the code does all kinds of wonderful things with it.
OK, it does some massaging and makes pretty diagrams. Problem is that if the initiation of the factory (i.e. do an API call, get some data, store it in a variable) haven't finished, half my page does not render, my boss gets angry, I get fired, and I'll have to dumpster dive behind McDonald (OK, not quite that bad).
So moved my init api call to a promise, sweet.
Then created a helper function that returns status (it checks if there is data in a variable and returns true or false). And I have the request it self (idGetSkill).
I then also have a directive which is called about 20 times, so I don't really want to call my init 20 times in order to ensure that there is data. I only whant to call it if the data is empty, or of skillLoaded returns false.
But if I use .then as a callback, I need a promise that resolves for it to run. So was thinking.
An example of where it could be used:
The Factory
.factory('skillFactory', function($http) {
var skillFactory = [];
var skills = [];
var searchId = [];
var mySkillId = [];
skillFactory.init= function() {
console.log("Got called")
return $http.get('/api/skillList')
.then(function(data){
skills=data['data']
console.log('Skill test ' + skills[0].alias );
})
}
skillFactory.skillLoaded=function(){
if(skills.length < 1) {
console.log("Warning, no data");
return false;
} else {
return true;
}
}
skillFactory.idGetSkill = function(data) {
if (skills.length < 1){
console.log("Warning, no data");
} else {
for (var id in skills) {
if (data == skills[id]._id) {
return skills[id];
}
}
}
}
}
And an app calling it
.controller("PromiseLoad", function ($scope, $http, $window, skillFactory ) {
var promise;
if( ! skillFactory.skillLoaded() ){
promise = skillFactory.init();
} else {
promise = skillFactory.init();
}
var skill = '55c8a069cca746f65c9836a1'
console.log("Will ask for skill " + skill)
promise.then( function() {
console.log("Im done waiting!")
$scope.answer = skillFactory.idGetSkill(skill);
console.log ("And got " +$scope.answer.alias);
})
});
(OK, the above example does not really need it, but its easier to provide this example rather than a directive as that needs a lot more things to work. Like data and stuff:) )
$q.when(data) returns a resolved promise.
$q.reject(data) returns a rejected promise.
It seems that factory methods execution priority is the highest, so that callbacks has no data to deal with. What is the best way to make this work?
I got this kind of factory
app.factory('jsonService', function($http) {
return {
getDistricts: function(callback) {
$http.get('data/districts.json').success(callback);
},
getLocations: function(path,callback) {
$http.get('data/'+path+'.json').success(callback);
}
};
});
And controller
var app = angular.module('sandbox', []);
app.controller('sandboxCtrl',function ($scope,jsonService) {
//This one works
$scope.init1= function(){
jsonService.getDistricts(function(data){
$scope.districts = data;
$scope.currentDistrict = $scope.districts[0].name;
jsonService.getLocations($scope.currentDistrict,function(data){
$scope.locations1 = data;
})
});
};
$scope.init1();
//This one does not
$scope.init2= function(){
jsonService.getDistricts(function(data){
$scope.districts = data;
$scope.currentDistrict = $scope.districts[0].name;
})
jsonService.getLocations($scope.currentDistrict,function(data){
$scope.locations1 = data;
});
};
$scope.init2();
});
Here is working plunker
Angular has an implementation of promises named $q (documentation) that you should read up upon.
There is a race condition due to the async nature of http calls. Please review the updated code linked to below that shows an example of your code running (successfully) using promises to handle your two calls in succession.
So upon success of your first call it will call your second service method all without using callbacks thanks to the power of promises.
jsonService.getDistricts()
.success(function(data) {
$scope.districts = data;
$scope.currentDistrict = $scope.districts[0].name;
jsonService.getLocations($scope.currentDistrict)
.success(function(locationData) {
$scope.locations = locationData;
})
});
updated PLKR
Promise clarification:
The raw implementation of basic promises uses then to handle responses and promises returned from $http add additional methods (success, error) that will unpack your data from the response object that you would need to handle if your just use then.
init1() is the correct way of doing this. init2() does work because jsonService.getLocations() is getting invoked before jsonService.getDistritcs() completes. The angular $http service is asynchronous. Since jsonService.getLocations() depends on data from jsonServicd.getDistricts() you must wait until .getDistricts() completes before calling .getLocations(). One way to do that is to call .getLocations() within the .getDitricts() callback, just as you did in init1().
I am creating a "Web Planner" where the user has to complete a number of steps on different pages in order to create an order for our service. I know that I need to use a service in order to share certain data around the entire Planner, but I am still not sure how do go about it.
The problem is that the user will not necessarily go in-order every time. They can stop on any page, then come back later and resume, therefore I need a way to manage some API data so that if the user is on 1 of 2 pages that share the same data, the data will fetched from the server via API or simply assigned if the data was already taken.
Right now the only method I thought of is to place the API requests inside my Service as well, and then when I run something like OrderService.get() the service will handle logic to check if the data was already grabbed from the server. If it was then it is just a simple assignment like $scope.model = OrderService.get(), but then the problem is that if the data isn't loaded yet, then I need to use a promise somewhere to wait for the data, so a simple assignment operation won't suffice.
This service may look something like this:
app.factory('OrderService', function(){
var orders = []; // This is the actual data
var service = {};
service.get = function(id){
if(orders.length){
return orders; // This means there is already data available
}else{
// This is where I am not sure what to do...
// Maybe...
var promise = API.Orders.getAllOrders({id : id}, function(res){
// Not sure about how to implement this part
}).$promise;
return promise;
}
}
})
Does anyone have any other ideas?
Since your calls are asynchronous, the only way to do this is via promises
app.service('OrderService', function($q){ // import $q ; the angularjs promise
var orders = []; // This is the actual data
var service = {};
service.get = function(id){
var deferred = $q.defer(); // create a unit of work to be done; a promise
if(orders.length){ // if data is already available, resolve the promise
deferred.resolve(orders); // This means there is already data available, so mark as resolved and successful
}else{
// make your async call since you don't have data
API.Orders.getAllOrders({id : id}, function(response){
// here you can actually modify the response before it goes back to your controller
deferred.resolve(response);
}, function(reason){ // failure scenario
deferred.reject(reason);
})
}
return deferred.promise; // always return promise
}
})
Just return a promise all the time, whether the data is already there, or fetched by a call to a server. In your example..
if(orders.length){
return $q.when(orders); // This means there is already data available
}else{
// This is where I am not sure what to do...
// Maybe...
var promise = API.Orders.getAllOrders({id : id}, function(res){
// Not sure about how to implement this part
}).$promise;
return promise;
}
Please bear in mind that I'm still a beginner (reading through a lot of documentation) so this might not be 100% correct.
Your service would be something like this:
app.factory('OrderService', function($q){
var orders = []; // This is the actual data
return {
getData: function(id){
var deferred = $q.defer();
if(orders.length){
deferred.resolve(orders); // This means there is already data available
}else{
$http.get('/api/get/data/' + id)
.success(function(data) {
deferred.resolve(data);
})
.error(function(){
deferred.reject();
});
}
return deferred.promise;
}
})
And then on your controllers you just need to use it like this:
app.controller('MainCtrl', function($scope, OrderService) {
var id = 1;
$scope.data = OrderService.getData(id);
});
I have a service in Angular that fetches a list of querys from a database table. The user selects one and moves on to another view to work with it. When the user clicks back to the first view though, I'd like to avoid re-fetching the query list. The query list is stored in the service but I'm having a hard time handing it back to the controller given that my fetch routine uses .then and promises.
app.service('queryService', function ($http)
{
var querys = new Object();
this.loadQueryList = function()
{
if (querys!=null)
{
//how to return the querys list here? caller expecting a promise
}
var promise = $http.post("php/datapump.php", { action: "FETCH", item: "QUERYS", id1: null, id2: null})
.then(function (response)
{
querys=response.data;
return querys;
});
return promise; // Return the promise to the controller
};
});
In the controller initialization the service is called with:
queryService.loadQueryList().then(function(d)
{
$scope.querys = d;
$scope.selectedquery=queryService.getSelectedQuery();
});
Or is there a better method altogether? I can imagine setting some flag in the service to get around this but it seems ugly. I simply want to avoid fetching the data a 2nd time.
You could create your own promise and resolve it immediately. This is much nicer than setting a flag and returning some cached data directly because it keeps your service interface consistent - that method always returns a promise.
var deferred = $q.defer();
deferred.resolve(yourdata);
return deferred.promise;
You will obviously need to inject $q into your service.
I am using some data which is from a RESTful service in multiple pages.
So I am using angular factories for that. So, I required to get the data once from the server, and everytime I am getting the data with that defined service. Just like a global variables. Here is the sample:
var myApp = angular.module('myservices', []);
myApp.factory('myService', function($http) {
$http({method:"GET", url:"/my/url"}).success(function(result){
return result;
});
});
In my controller I am using this service as:
function myFunction($scope, myService) {
$scope.data = myService;
console.log("data.name"+$scope.data.name);
}
Its working fine for me as per my requirements.
But the problem here is, when I reloaded in my webpage the service will gets called again and requests for server. If in between some other function executes which is dependent on the "defined service", It's giving the error like "something" is undefined. So I want to wait in my script till the service is loaded. How can I do that? Is there anyway do that in angularjs?
You should use promises for async operations where you don't know when it will be completed. A promise "represents an operation that hasn't completed yet, but is expected in the future." (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise)
An example implementation would be like:
myApp.factory('myService', function($http) {
var getData = function() {
// Angular $http() and then() both return promises themselves
return $http({method:"GET", url:"/my/url"}).then(function(result){
// What we return here is the data that will be accessible
// to us after the promise resolves
return result.data;
});
};
return { getData: getData };
});
function myFunction($scope, myService) {
var myDataPromise = myService.getData();
myDataPromise.then(function(result) {
// this is only run after getData() resolves
$scope.data = result;
console.log("data.name"+$scope.data.name);
});
}
Edit: Regarding Sujoys comment that
What do I need to do so that myFuction() call won't return till .then() function finishes execution.
function myFunction($scope, myService) {
var myDataPromise = myService.getData();
myDataPromise.then(function(result) {
$scope.data = result;
console.log("data.name"+$scope.data.name);
});
console.log("This will get printed before data.name inside then. And I don't want that.");
}
Well, let's suppose the call to getData() took 10 seconds to complete. If the function didn't return anything in that time, it would effectively become normal synchronous code and would hang the browser until it completed.
With the promise returning instantly though, the browser is free to continue on with other code in the meantime. Once the promise resolves/fails, the then() call is triggered. So it makes much more sense this way, even if it might make the flow of your code a bit more complex (complexity is a common problem of async/parallel programming in general after all!)
for people new to this you can also use a callback for example:
In your service:
.factory('DataHandler',function ($http){
var GetRandomArtists = function(data, callback){
$http.post(URL, data).success(function (response) {
callback(response);
});
}
})
In your controller:
DataHandler.GetRandomArtists(3, function(response){
$scope.data.random_artists = response;
});
I was having the same problem and none if these worked for me. Here is what did work though...
app.factory('myService', function($http) {
var data = function (value) {
return $http.get(value);
}
return { data: data }
});
and then the function that uses it is...
vm.search = function(value) {
var recieved_data = myService.data(value);
recieved_data.then(
function(fulfillment){
vm.tags = fulfillment.data;
}, function(){
console.log("Server did not send tag data.");
});
};
The service isn't that necessary but I think its a good practise for extensibility. Most of what you will need for one will for any other, especially when using APIs. Anyway I hope this was helpful.
FYI, this is using Angularfire so it may vary a bit for a different service or other use but should solve the same isse $http has. I had this same issue only solution that fit for me the best was to combine all services/factories into a single promise on the scope. On each route/view that needed these services/etc to be loaded I put any functions that require loaded data inside the controller function i.e. myfunct() and the main app.js on run after auth i put
myservice.$loaded().then(function() {$rootScope.myservice = myservice;});
and in the view I just did
ng-if="myservice" ng-init="somevar=myfunct()"
in the first/parent view element/wrapper so the controller can run everything inside
myfunct()
without worrying about async promises/order/queue issues. I hope that helps someone with the same issues I had.