How to use an Angular service to share data across controllers/pages? - angularjs

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

Related

How to chain promises returned from Services in AngularJS

According to best practices I have created a service for all different chunks of API call and call the file as api.js;
Here is one example.
api.service('Auth', function(Config, $http, $q, $state) {
this.login = function(jsonToSend) {
return $http.post(Config.apiurl + '/user/auth/login', jsonToSend)
.then(function(response) {
return response.data;
}, function(error) {
return $q.reject(error.data);
});
};
// Api function for signing the user up
this.signup = function(jsonToSend) {
return $http.post(Config.apiurl + '/user/auth/signup', jsonToSend)
.then(function(response) {
return response.data;
}, function(response) {
return $q.reject(response.data);
});
};
});
Now in my loginController.js I inject this service with other services. Now what I want to is.
Auth.Login()
.User.getUser()
.User.getCart()
.Item.getProduct()
My kiddish attempt. I know it's wrong but the community asks you to show what you did.
var q = $q.defer() //intialize
Auth.login().then(function(response){
//some manipulation and store stuff in data
$q.resolve(data);
})
.then(function(data){
})
I want to chain them like this. I know the representation is wrong, but I hope you get the idea of what I am trying to do here.
I have another option of doing this via waterfall method of async.js/ underscore.js but I want to improve my knowledge on promises and hence would like to know if this approach is possible.
Thanks.
You can to save the promises to vars like this:
var loginPromise = Auth.login();
var userPromise = User.getUser();
var chartPromise = User.getCart();
var productPromise = Item.getProduct();
Then you can wait for all promises to resolve and do something with the results like this:
$q.all([loginPromise, userPromise , chartPromise , productPromise]).then(function(data){
/* You can Access each promise data with the index of the order you put them into $q.all(..
* data[0], data[1], data[2], data[3]
*/
});

Angular response objects and storing the data

I'm trying to take the response of an $http request and save it to a custom cache. I want to then use that cache to display data into the view. I thought the cache would be checked automatically on each request before fetching new data, but that doesn't seem to be working for me.
The problem I'm having is I can't seem to save the data. The following function needs to make 2 requests: articles and images.
getImages: function() {
var cache = $cacheFactory('articlesCache');
$http.get(posts)
.then(function (data) {
var articles = data;
angular.forEach(articles, function (article) {
var imageId = {id: article.image_id};
$http.post(images, imageId)
.then(function (response) {
article.image = response;
cache.put(article.url, article);
});
});
});
return cache;
}
This creates the custom cache, but there's no data in the returned object. I know now that I can't save the data this way, but I don't know why or how I would go about doing it.
Can anyone explain how storing response data works? Where, if at all, does using promises come in here?
Your return statement executes before the code in your then function does. If you want to return the cache you'll want to run everything through the $q service and then return the resolved promise.
This is probably not the best way to use $cacheFactory. Typically you'd expose your cache as a service at a higher level and then access the cache via the service where needed.
So on your main module you'd have something like this to create the cache.
.factory('cache', function ($cacheFactory) {
var results = $cacheFactory('articleCache');
return results;
})
Then where ever you need the cache you inject it into the controller and use cache.get to retrieve the data from it.
If you want to use $q to implement this, your code would look something like the code below. (Disclaimer: I've never used $q with $cacheFactory like this, so without all of your components, I can't really test it, but this should be close.)
var imageService = function ($http, $q,$cacheFactory) {
var imageFactory = {};
imageService.cache = $cacheFactory('articlesCache');
imageFactory.getImages = function () {
var images = $q.defer();
$http.get(posts)
.then(function (data) {
var articles = data;
angular.forEach(articles, function (article) {
var imageId = {id: article.image_id};
$http.post(images, imageId)
.then(function (response) {
article.image = response;
cache.put(article.url, article);
});
images.resolve(cache.get('articlesCache'))
});
});
return images.promise
app.factory('ImageService', ['$http', '$q', '$cacheFactory', imageService]);
});
I adapted the code from this answer: How to get data by service and $cacheFactory by one method
That answer is just doing a straight $http.get though. If I understand what you're doing, you already have the data, you are posting it to your server and you want to avoid making get call to retrieve the list, since you have it locally.

Angular design pattern for self fulfilling promise

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.

How to pass already-fetched data to a controller when it's expecting a promise

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.

How to wait till the response comes from the $http request, in angularjs?

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.

Resources