Store data from asynchronous call to a variable in AngularJS - javascript

Following is the code in which I am trying to use promise to save data from an asynchronous call to a variable but its not working. I am new to promise as I serached and found promise helps in these cases but I am unable to apply, let me know what I am doing wrong here -
angular.module("app").controller("myCtrl", function($scope, $http, $q) {
var deferred = $q.defer();
var data = $http.get("/api/events").success(function(response){
deferred.resolve(response);
return deferred.promise;
// ALSO tried return response;
})
console.log("DATA--");
console.log(data);
});
EDIT -
I am trying to hit two APIS -
1) Create array of ids from 1st API hit.
2) Loop to the array for 2nd API hit on id basis.
3) Concatenate some data from array 1 and array2.
More specific case, which I am trying to do but found to use promise -
http://pastebin.com/ZEuRtKYW

I would do it as follows:
$http.get('data.json').success(function (result) {
$scope.result = result;
}).then(function (data) {
var result = data.data.key;
var promises = result.map(function (val) {
var deffered = $q.defer();
$http({
method: 'GET',
url: 'data-' + val.id + '.json'
})
.success(function (x) {
deffered.resolve(x);
})
.error(function () {
deffered.reject();
});
return deffered.promise;
});
$q.all(promises).then(function (result) {
$scope.resolvedData = result;
});
});
Map all the promises into a promises array based on the result of the first call. Inside this map function create a new promise and resolve it in the success function. Make sure you return the actual promise!
Afterwards you can get all the resolved data with $q.all(promises). This way your calls to the database are not limited to 2. You can do as much calls as you want based on the data retrieved in the first call.
Plunker
Edit: Not sure if you're able to modify the service, but it would be better if you can achieve this with only one call. For instance:
get '/api/events' --> returns just the events
get '/api/events?includeDetail=true' --> returns the events + the details of an event

When you get response in asynchronous call, store it in scope variable. Then you can access that scope variable anywhere inside controller.
angular.module("app").controller("myCtrl", function($scope, $http) {
$http.get("/api/events").success(function(response){
$scope.response = response;
})
});

I think this can be done somthing like as:
angular.module("app").controller("myCtrl", function($scope, $http, $q) {
var ids = [],
q1 = $q.defer(),
q2 = $q.defer();
d1 = $http.get("/api/events").success(function(data){
q1.resolve(data);
angular.forEach(data, function(id) {
ids.push(id);
});
}).error(function(err){
q1.reject("Ooops!!")
});
var promises = [];
angular.forEach(ids, function(id) {
var promise = $http.get("/api/events", {id:id})
.success(function(data){
q2.resolve(data);
}).error(function(err){
q2.reject("Ooops!!")
});
promises.push(promise);
});
$q.all(promises)
.then(function(values) {
console.log(values);
});
});

Related

Manipulate data from promise result

var prom = $http.get('url');
prom.success(function(data){
//here calling some service and updating the data
});
$scope.abc = function(){
//doing some calculation with updated data
}
Once i get data from http request, i am calling some service which adds some info to received data. i.e data+service()=updatedData.
Now,on this updated data I am applying some calculation, and displaying it in view.
But this thing is not working. I tried to add this calculation to prom.success itself, but still I am not getting updated data. I tried setInterval() to $scope.abc but some time it display sometime won't.
Please help me to solve this problem.
Thank you
Write a method in a service that will resolve the data. In the method first call $http to get the data. After it has got the data, call the 2nd service which will add extra info. Once that is done (i.e. the promise for the 2nd service has been resolved), resolve the update data. From the controller call this method in the service, and wait for it to be resoved. When it's resoved; 'then' call your method on scope that will display the data. I will post a working fiddle for you.
You can check the fiddle here.
Service
myApp.factory('service', function($http, $q) {
var addInfoToData = function(data) {
var deferred = $q.defer();
data.updatedInfo = "Some dummy info";
deferred.resolve(data);
return deferred.promise;
};
//Dummy Method for $http call
var callApi = function() {
var deferred = $q.defer();
var dummyData = {
Id: 1,
Name: "Fake Info",
Value: 15
};
deferred.resolve(dummyData);
return deferred.promise;
}
var getData = function() {
var deferred = $q.defer();
//Replace with $http
callApi().then(function(data) {
addInfoToData(data).then(function(updatedData) {
deferred.resolve(data);
})
})
return deferred.promise;
}
return {
getData: getData
}
})
Note: callApi method is a dummy method for $http call. Replace it with your actual API call.
Controller
function MyCtrl($scope, service) {
$scope.name = 'Superhero';
$scope.abc = function(data) {
data.Value = data.Value / 100;
$scope.displayData = data;
}
var getData = function() {
service.getData().then(function(data) {
$scope.abc(data);
})
}
getData();
}

Execute function after another AngularJS

I need to execute a function which fetches data after a kind of login function who provides the sessionId. This sessionId is necessary for the second function.
app.controller('TestController',
function ($scope, dbObjectsDAO, loginService){
var sessionID = loginService.getSessionID(); //Login function
var self = this;
this.items = [];
this.constructor = function() {
dbObjectsDAO.getAll(sessionID).then(function(arrObjItems){
$scope.items = arrObjItems;
});
};
this.constructor(); //get the data
return this;
});
I tried several variations like:
loginService.getSessionID().then(function(sessionID){
this.constructor(); //also with just constructor();
});
But I always receive errors (in the case above: Illegal constructor).
So how can I manage to execute one function after another ? Maybe a callback structure would help here but I have no clue how to realize it.
EDIT
Here is the code for the login:
app.service('loginService', function($http, $q) {
this.getSessionID = function()
{
return $http({
method: "GET",
url: "http://localhost:8080/someRequestDoneHere"
}).then(function(response)
{
return response.data.sessionId; // for example rYBmh53xbVIo0yE1qdtAwg
});
};
return this;
});
Does your getSessionID() function return a promise? If so you want code like this:
app.controller('TestController',
function ($scope, dbObjectsDAO, loginService){
var sessionID;
var vm = this;
vm.items = [];
loginService.getSessionID()
.then(function(sid) {
sessionID = sid;
return dbObjectsDAO.getAll(sessionID);
})
.then(function(arrObjItems){
vm.items = arrObjItems;
});
});
So your login service returns a promise which resolves to the session id. You can save that in a variable for use elsewhere, and also use it to trigger fetching the items.
I also changed your self to vm as that naming is an Angular convention, and stored the items in vm.items rather than directly in the scope.
Edit:
Your login code already returns a promise, not a session id. return inside a then is simply going to return a new promise that resolves to the value you are returning.
There are several ways to chain multiple $http requests. If they are independent of each other just fire off a bunch of requests and use $q.all to handle when they have all completed.
var promise1 = $http(something)
.then(function(response) { vm.data1 = response.data; return vm.data1; });
var promise2 = $http(something)
.then(function(response) { vm.data2 = response.data; return vm.data2; });
$q.all([promise1, promise2], function(values) {
// here can safely use vm.data1 or values[0] as equivalent
// and vm.data2 or values[1].
});
If one request depends on the result of another you could even do this:
var promise1 = $http(something)
.then(function(response) {
vm.data1 = response.data;
return { method:'GET', url: response.data.link}
});
var promise2 = promise1.then($http)
.then(function(response) { vm.data2 = response.data; return vm.data2; });
Your template needs to declare the controller using the 'controller as something' syntax:
<div ng-controller="TestController as test" ng-bind="test.items"></div>
Have you tried to nest the second function, like this ? without the constructor call ?
loginService.getSessionID().then(function(sessionID){
dbObjectsDAO.getAll(sessionID).then(function(arrObjItems){
$scope.items = arrObjItems;
});
});
Mb you have wrong scope in
..then(function(sessionID){...}) ?
you can try some this like this:
var vm=this;
loginService.getSessionID().then(function(sessionID){
vm.constructor();
});

AngularJS : Why when using back button on the browser my promises return before resolve with previous value?

Hi here is my code when I run the factory this one gets resolved before with the old data from the page before. if I put break points I get a hit on the callback before the promise is resolved
$scope.formElementsData = response; get there before deferred.resolve(formElements);.
// when I call it after using the back button
GetInputControlItemsService.getInputControlItems($routeParams.project +','+ $routeParams.templateid ).then(function(response) {
$scope.formElementsData = response;
});
app.factory('GetInputControlItemsService', [
'$http', '$q',
function($http, $q) {
var apiCall, deferred, factory, _getInputControlItems;
factory = {};
deferred = $q.defer();
apiCall = 'api/GetProjectInputControlItems/?projectAndTempletaeId=';
_getInputControlItems = function(projectAndTempletaeId) {
$http.get(webBaseUrl + apiCall + projectAndTempletaeId).success(function(formElements) {
deferred.resolve(formElements);
}).error(function(err) {
deferred.reject(err);
});
return deferred.promise;
};
factory.getInputControlItems = _getInputControlItems;
return factory;
}
])
Y also tried
GetInputControlItemsService.getInputControlItems($routeParams.project +','+ $routeParams.templateid ).then(function(response) {
return response;
}).then(function(response){
$scope.formElementsData = response;
});
and still not working any idea how to use this along with the back button of the browser ?
Also I notice if I call a factory inside a function for a search feature, it does the same thing it runs inside the callback before the promise is deferred.
So I end up calling it without promises and now it works here is the code without the factory service.
$scope.goSearch = function() {
if ($scope.searchKey !== '') {
$http.get(webBaseUrl + apiCall + $scope.searchKey).success(function(results) {
$scope.projects = results;
});
}
};

Recursive queries with promises in AngularJS

I have a recursive query that needs to potentially make further queries based on the results. I would ideally like to be able to construct a promise chain so that I know when all of the queries are finally complete.
I've been using the example from this question, and I have the following method:
this.pLoadEdges = function(id,deferred) {
if (!deferred) {
deferred = $q.defer();
}
$http.post('/Create/GetOutboundEdges', { id: id }).then(function(response) {
var data = response.data;
if (data.length > 0) {
for (var i = 0; i < data.length; i++) {
var subID = data[i].EndNode;
edgeArray.push(data[i]);
self.pLoadEdges(subID, deferred);
}
} else {
deferred.resolve();
return deferred.promise;
}
});
deferred.notify();
return deferred.promise;
}
Which I then start elsewhere using:
self.pLoadEdges(nodeID).then(function() {
var edgedata = edgeArray;
});
And of course I intend to do some more stuff with the edgeArray.
The problem is that the then() function is trigged whenever any individual path reaches an end, rather than when all the paths are done. One particular pathway might be quite shallow, another might be quite deep, I need to know when all of the pathways have been explored and they're all done.
How do I construct a promise array based on this recursive query, ideally so that I can use $q.all[] to know when they're all done, when the number of promises in the promise array depends on the results of the query?
I'm not 100% positive what the end result of the function should be, but it looks like it should be a flat array of edges based on the example that you provides. If that's correct, then the following should work
this.pLoadEdges = function(id) {
var edges = [];
// Return the result of an IIFE so that we can re-use the function
// in the function body for recursion
return (function load(id) {
return $http.post('/Create/GetOutboundEdges', { id: id }).then(function(response) {
var data = response.data;
if (data.length > 0) {
// Use `$q.all` here in order to wait for all of the child
// nodes to have been traversed. The mapping function will return
// a promise for each child node.
return $q.all(data.map(function(node) {
edges.push(node);
// Recurse
return load(node.EndNode);
});
}
});
}(id)).then(function() {
// Change the return value of the promise to be the aggregated collection
// of edges that were generated
return edges;
});
};
Usage:
svc.pLoadEdges(someId).then(function(edgeArray) {
// Use edgeArray here
});
You need $q.all function:
Combines multiple promises into a single promise that is resolved when all of the input promises are resolved.
Update 1
Check this demo: JSFiddle
The controller can be like following code (well, you may want to put it in a factory).
It loads a list of users first, then for each user, load the posts of this user. I use JSONPlaceholder to get the fake data.
$q.all accepts an array of promises and combine them into one promise. The message All data is loaded is only displayed after all data is loaded. Please check the console.
angular.module('Joy', [])
.controller('JoyCtrl', ['$scope', '$q', '$http', function ($scope, $q, $http) {
function load() {
return $http.get('http://jsonplaceholder.typicode.com/users')
.then(function (data) {
console.log(data.data);
var users = data.data;
var userPromises = users.map(function (user) {
return loadComment(user.id);
});
return $q.all(userPromises);
});
}
function loadComment(userId) {
var deferred = $q.defer();
$http.get('http://jsonplaceholder.typicode.com/posts?userId=' + userId).then(function (data) {
console.log(data);
deferred.resolve(data);
});
return deferred.promise;
}
load().then(function () {
console.log('All data is loaded');
});
}]);
Update 2
You need a recursive function, so, check: JSFiddle.
The code is below. I use round to jump out of the recursion because of the fake API. The key is here: $q.all(userPromises).then(function () { deferred.resolve(); });. That tells: Please resolve this defer object after all promises are resolved.
angular.module('Joy', [])
.controller('JoyCtrl', ['$scope', '$q', '$http', function ($scope, $q, $http) {
var round = 0;
function load(userId) {
return $http.get('http://jsonplaceholder.typicode.com/posts?userId=' + userId)
.then(function (data) {
var deferred = $q.defer();
console.log(data.data);
var posts = data.data;
if (round++ > 0 || !posts || posts.length === 0) {
deferred.resolve();
} else {
var userPromises = posts.map(function (post) {
return load(post.userId);
});
$q.all(userPromises).then(function () {
deferred.resolve();
});
}
return deferred.promise;
});
}
load(1).then(function () {
console.log('All data is loaded');
});
}]);
You can try building up an array of returned promises and then use the $.when.apply($, <array>) pattern. I've used it before to accomplish a similar thing to what you're describing.
More info on this SO thread.
UPDATE:
You probably also want to read the docs on the apply function, it's pretty neat.

Is it possible to pass GET response data back to same factory?

Question:
From any controller, how can I call the getPages function, return the data back to the controller and replace the empty Page.details.refobject with the GET response data?
is it possible for this all to happen within the factory regardless of which controller calls the function?
app.factory('Pages', function($http, ENV){
var Pages = {};
Pages.details =
{
pages:
{
length: 0,
offsets: []
},
ref:
{
//data goes here on success
},
getPages: function($scope) {
return $http.get(ENV.apiEndpoint + '/' + $scope.storeSlug + '/pages.json?code=' + $scope.promoCode)
.success(function(data){
// I want this Pages.details.ref to be replaced on success of getPages
Pages.details.ref = data;
$scope.handlePagesSuccess(data);
return data;
})
.error(function(data, status){
// console.log('error:' + status);
});
}
}
return Pages;
});
Controllers:
this controller calls the init request
app.controller('RandomCtrl', function($scope, Pages){
var handleSuccess = function (data) {
$scope.data = data;
}
Pages.details.getPages($scope).success(handleSuccess);
})
Controller #2:
this controller just consumes a temp version of the request no relationship between the RandomCtrl. e.g this controller is typically a directive level controller where the theres no bubbling between a parent ctrl
app.controller('OtherCtrl', function($scope, Pages){
$scope.tempPage = Pages.details.ref;
})
it shouldnt matter where getPages is called from. I want ref to be replaced everytime getPages is called.
It seems like you are trying to manage state inside your factory, which probably is not a good idea. Also it is not a good idea to pass around $scope in factories. They should be limited to its own controller. You could instead cache the promise for the previous call made and based on a flag you could either return the cached promise or make the actual service call.
app.factory('Pages', function($http, ENV, $q){
var Pages = {};
var cachedPromise = {};
Pages.details =
{
pages:
{
length: 0,
offsets: []
},
getPages: function(request) {
//Get a request key to make sure you are returning right promise incase multiple product calls are made at the same time.
var reqKey = request.storeSlug + request.promoCode;
//if a call has already been made and there is a promise return it
if(cachedPromise[reqKey]) return cachedPromise[reqKey];
//Store the promise in the cache for lastCall retrieval
return cachedPromise[reqKey] = $http.get(ENV.apiEndpoint + '/' + request.storeSlug + '/pages.json?code=' + request.promoCode)
.then(function(result){
return result.data; //You can alter data and send as well
}, function(data, status){
return $q.reject('some error'); //or return some data
}).finally(function(){
//remove the cache from the map, once promise is resolved.
delete cachedPromise[reqKey];
});
}
}
return Pages;
});
In your first controller do:-
app.controller('RandomCtrl', function($scope, Pages){
//Build your request.
Pages.details.getPages(request).then(function (data) {
$scope.data = data;
});
});
In your second controller just do the same:-
app.controller('OtherCtrl', function($scope, Pages){
//Pass the flag to get the cached data.
Pages.details.getPages(request).then(function (data) {
$scope.tempPage = data;
});
});

Categories

Resources