I want to populate some values in Json that are being calculated with angular-promises and these value should be updated after certain events.
I tried to call the factory which yields the values for example something like below and tried to call the functions GetWeeklyVal and GetDailyVal which are in charge of calculating the values :
this.salesList =
{"sales":[
{ "id":"A1", "dailyValue": GetDailyVal('A1'), "weeklyValue": GetWeeklyVal('A1')},
{ "id":"A2", "dailyValue": GetDailyVal('A2'), "weeklyValue": GetWeeklyVal('A2')}
]}
and in my controller I have:
$scope.sales= salesServices.salesList.sales;
but it didn't work. the values remain zero which is the default value in the application.
Why the values are not being updated and what would be a better solution?
update
This is the portion of the code I call the calculation functions: (I skip the portion to get the values based on passed id in here)
function GetDailyVal(id){
var dValue = 0;
salesService.getSales();
dValue = salesService.totalAmount;
return dValue;
}
this is the factory
.factory('salesService', ['$http', '$q'],
function salesInvoiceService($http, $q) {
var service = {
sales: [],
getSales: getSales,
totalAmount: 0
};
return service;
function getSales() {
var def = $q.defer();
var url = "http://fooAPI/salesinvoice/SalesInvoices"; //+ OrderDate filter
$http.get(url)
.success(function(data) {
service.sales = data.d.results;
setTotalAmount(service.sales);
def.resolve(service.sales);
})
.error(function(error){
def.reject("Failed to get sales");
})
.finally(function() {
return def.promise;
});
}
function setTotalAmount(sales){
var sum = 0;
sales.forEach(function (invoice){
sum += invoice.AmountDC;
});
service.totalAmount = sum;
}
})
I think there are some errors in your code.
I give some sample code here. I think this will help you.
This is a sample code in one of my application. Check it.
service.factory('Settings', ['$http','$q', function($http,$q) {
return {
AcademicYearDetails : function(Details) {
return $http.post('/api/academic-year-setting', Details)
.then(function(response) {
if (typeof response.data === 'object') {
return response.data;
} else {
return $q.reject(response.data);
}
}, function(response) {
return $q.reject(response.data);
});
},
newUser : function(details) {
return $http.post('/api/new-user', details);
}
}
}]);
The reason why its not working is:
dailyValue: GetDailyVal('A1')
Here, GetDailyVal makes an async ajax call to an api. For handling async requests, you have to return a promise as follows in your GetDailyVal function as follows:
function GetDailyVal() {
salesService.getSales().then(function(data) { //promise
dValue = salesService.totalAmount;
return dValue;
})
}
Same thing need to be done for weeklyValue.
Related
I am fetching the details from an API using Angular Service and I want to return only the matching items. Meaning, after I type some text, I will click a button, which will call a function in a Controller which will call the Service to get the details.
Now my problem, is that it returns the entire list not the filtered list, I have stored filter result into an array I want to return that array(foundItems).
This is my code
(function() {
angular.module('NarrowItDownApp',[])
.constant('url',"https://davids-restaurant.herokuapp.com/menu_items.json")
.controller('NarrowItDownController',['$scope','MenuSearchService',function($scope,MenuSearchService) {
var items=this;
items.searchitem="";
items.getitems=function() {
MenuSearchService.getMatchedMenuItems(items.searchitem).then(function(response) {
this.found=response.data;
console.log(this.found);
})
.catch(function(response) {
this.found=response.data;
console.log(this.found);
});
};
}])
.service('MenuSearchService',['$http','url',function($http,url) {
var service=this;
service.getMatchedMenuItems=function(searchitem)
{
var foundItems=[];
var key;
return $http.get(url).success(function(data) {
for(var i=0;i<data.menu_items.length;i++)
{
var temp=data.menu_items[i];
//Old method
/*for(key in temp)
{
if(temp.hasOwnProperty(key))
{
console.log(temp[key])
}
}*/
Object.keys(temp).forEach(function(items)
{
if(searchitem==temp[items])
{
foundItems.push(temp);
}
});
};
console.log(foundItems);
return foundItems;
})
.error(function(data) {
console.log('error');
return data;
});
return foundItems;
};
}]);
})();
Now my problem, is that it returns the entire list not the filtered list, I have stored filter result into an array I want to return that array(foundItems).
The reason that the service returns the entire list is that the .success and .error methods ignore return values. Use .then and .catch instead.
service.getMatchedMenuItems=function(searchitem)
{
var foundItems=[];
var key;
//return $http.get(url).success(function(data) {
//USE .then method
return $http.get(url).then(function(response) {
var data = response.data;
for(var i=0;i<data.menu_items.length;i++)
{
var temp=data.menu_items[i];
Object.keys(temp).forEach(function(items)
{
if(searchitem==temp[items])
{
foundItems.push(temp);
}
});
};
console.log(foundItems);
return foundItems;
})
//.error(function(data) {
//USE .catch method
.catch(function(errorResponse) {
console.log(errorResponse.status);
//return data;
//THROW to chain rejection
throw errorResponse;
});
//return foundItems;
};
Also it is important to use a throw statement in the rejection handler. Otherwise the rejected promise will be converted to a successful promise.
For more information, see Angular execution order with $q.
You can use promise for that Promise
(function() {
angular.module('NarrowItDownApp', [])
.constant('url', "https://davids-restaurant.herokuapp.com/menu_items.json")
.controller('NarrowItDownController', ['$scope', 'MenuSearchService', function($scope, MenuSearchService) {
var items = this;
items.searchitem = "";
items.getitems = function() {
MenuSearchService.getMatchedMenuItems(items.searchitem).then(function(data) {
for (var i = 0; i < data.menu_items.length; i++) {
var temp = data.menu_items[i];
//Old method
/*for(key in temp)
{
if(temp.hasOwnProperty(key))
{
console.log(temp[key])
}
}*/
Object.keys(temp).forEach(function(items) {
if (searchitem == temp[items]) {
foundItems.push(temp);
}
});
};
this.found = foundItems);
console.log(this.found);
})
.catch(function(response) {
this.found = response.data;
console.log(this.found);
});
};
}])
.service('MenuSearchService', ['$http', 'url', function($http, url) {
var service = this;
service.getMatchedMenuItems = function(searchitem) {
var foundItems = [];
var key;
return $http.get(url);
};
}]);
})();
I got stuck with learning angular in particular the $filter('filter') function and how to pass a local function to it as a filter function.
The API (json files) contain all the trips in trips.json and the id array of all the trips the user has been on in the user.trips.json.
The factory getting fetching the data looks like this:
app.factory("tripsApi", function ($http) {
return {
allTrips: $http.get('/api/trips.json')
.success(function (response) {
return response;
})
.error(function (error) {
return error;
}),
userTrips: $http.get('api/user.trips.json')
.success(function (response) {
return response;
})
.error(function (error) {
return error;
})
}
});
The next piece of code is just a service to retrieve all the user trips information. The service uses a factory to access the API (in this case just the men json files). And it SHOULD filter through the trips information to retrieve only the ones the user has been on using the id array.
app.service("trips", function (tripsApi, $filter) {
var self = this;
this.user = {
trips: Array()
};
this.trips = Array();
this.getUserTrips = function () {
self.getAllTrips.then(function () {
tripsApi.userTrips.success(function (response) {
self.user.trips = $filter('filter')
(self.trips, self.containsChild(id, response));
});
});
};
this.getAllTrips = tripsApi.allTrips.success(function (response) {
self.trips = response;
});
this.containsChild = function (id, idsArray) {
if (id != 0 && idsArray != null) {
for (var i = 0; i < idsArray.length(); i++) {
if (idsArray[i] == i)
return true;
return false;
}
}
}
});
Yet I can't get it to work. The first error I get is the id not defined in the self.containsChild(id, response);
Where are the mistakes? Any help is welcome :)
The id issue, the call should be:
$filter('filter')(self.trips, self.containsChild); // this won’t really work, though, because your `containsChild` function is not properly defined.
Second, This is not a properly defined service. You want something that looks like the following
app.service('ServiceName', [ '$filter', function ($filter) {
var myInstance = {};
myInstance.containsChild = function (value, index, array) {
};
myInstance.user = . . .;
return myInstance;
}]);
Third, fix your containsChild function to take three parameters. The first will be the value passed to it, the second will be the index, and the third will be the array being filtered.
Yet I can't get it to work. The first error I get is the id not
defined in the self.containsChild(id, response);
Where are the mistakes?
You passed id to the function but there's no idvariable declared anywhere in the code the you provided which is what's causing the error.
Also, I've observed that there's no consistency in your code, you assigned this to self but you keep using this and self everywhere in your code.
I am trying to write a loop which performs a number of http requests and adds each response to a list.
However, I don't think I am going about it quite the right way.
I think I am not implementing the required promises correctly. The console log after the for loop shows myList array as empty.
Code:
var _myList = []
function getStuff() {
var deferred = $q.defer()
var url = someUrl
$http.get(url).success(function(response) {
if ( response.array.length > 0 ) {
// loop starts here
for ( var i=0; i < response.array.length; i++ ) {
getThing(response.array[i].id);
};
// check the varibale here
console.log(_myList);
deferred.resolve('Finished');
} else {
deferred.resolve('No stuff exists');
};
}).error(function(error) {
deferred.reject(error);
});
return deferred.promise;
};
function getThing(thindId) {
var deferred = $q.defer()
var url = someUrl + thingId;
$http.get(url).success(function(response) {
_myList.push(response);
deferred.resolve(response);
}).error(function(error) {
deferred.reject(error);
});
return deferred.promise;
};
You can simplify your code as follows:
var allThings = response.array.map(function(id){
var singleThingPromise = getThing(id);
//return a single request promise
return singleThingPromise.then(function(){
//a getThing just ended inspect list
console.log(_myList);
})
});
$q.all(allThings).then(function(){
//only resolve when all things where resolved
deferred.resolve('Finished');
}, function(e){
deferred.reject('Something went wrong ' + e);
});
You indeed won't be able to populate _myList array with for-loop like you set up. Instead create an array of promises - one per data item in response.array and return it as inner promise.
function getStuff() {
var url = someUrl;
return $http.get(url).then(function(response) {
if (response.data.array.length > 0) {
return $q.all(response.data.array.map(function(data) {
return getThing(data.id);
}));
} else {
return 'No stuff exists';
}
});
}
function getThing(thindId) {
var url = someUrl + thingId;
return $http.get(url).then(function(response) {
return response.data;
});
}
After that you would use getStuff like this:
getStuff().then(function(myList) {
console.log(myList);
});
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.
I'm trying to do a caching factory for http requests, so it doesn't make the server do a lot of work for the same request. But It seems my way of using deferred "swallows" the data, and I don't know why.
Console output for below:
data fetched:
Object {state: "OK", data: Object, errorMessage: null, exception: null}
success
undefined
ImportFactory:
factory("importFactory", function ($http, $q, loggingService) {
return{
fetchedData: [],
cacheTransport: function (transportsId, data) {
this.fetchedData.push({"transportsId": transportsId, "data": data});
},
getImport: function (transportsId) {
var factory = this;
var deferred = $q.defer();
var preFetchedTransport = this.findTransport(transportsId);
if (preFetchedTransport === null) {
console.log('fetching from backend');
return $http.post("/import/create/" + transportsId).then(function (data) {
console.log('data fetched:');
console.log(data);
factory.cacheTransport(transportsId, data);
deferred.resolve(data);
});
}
preFetchedTransport = deferred.promise;
return preFetchedTransport;
},
findTransport: function (transportsId) {
for (var i = 0; i < this.fetchedData.length; i++) {
var transportObj = this.fetchedData[i];
if (transportObj.transportsId === transportsId) {
return transportObj.data;
}
}
return null;
}
};
});
Controller
.controller('ImportController', function ($scope, $routeParams, importFactory){
$scope.transportId = $routeParams.id;
importFactory.getImport($scope.transportId).then(function (successData) {
console.log('success');
console.log(successData);
}, function (errorData) {
console.log('error');
console.log(errorData);
});
You basically need this: Demo here.
var cachedPromises = {};
return {
getStuff: function(id) {
if (!cachedPromises[id]) {
cachedPromises[id] = $http.post("/import/create/" + id).then(function(resp) {
return resp.data;
});
}
return cachedPromises[id];
}
};
Now, when you fetch that data, you can manipulate and it will be changed when you access it in the future.
myService.getStuff(whatever).then(function(data) {
data.foo = 'abc';
});
//elsewhere
myService.getStuff(whatever).then(function(data) {
console.log(data.foo); // 'abc'
});
Here's a demo that does this, as well as a view updating trick (bind the object to the view before the data comes in), and an idea of how you could change the data separately from the cache, in case you want to have the original data and the changing data. http://jsbin.com/notawo/2/edit
Remember to avoid that nasty promise anti-pattern. If you already have a promise, use that instead of creating another with $q. $http already returns a promise and that promise is sufficient for whatever you need if you use it properly.
just change the loop condition look like this and then test i think your function and defer is work fine but the loop does not sent the correct data
for(var i = 0; i < this.fetchedData.length; i++) {
if (this.fetchedData[i].transportsId === transportsId) {
return this.fetchedData[i].data;
}
}
return null;
}
The reason you are getting undefined is you are not returning anything from the $http.post().then() !
Also in your getImport() function you are returning an empty promise when the transport is already cached. You need to resolve it to your already cached transport object.
getImport: function (transportsId) {
var factory = this;
var deferred = $q.defer();
var preFetchedTransport = this.findTransport(transportsId);
if (preFetchedTransport === null) {
console.log('fetching from backend');
return $http.post("/import/create/" + transportsId).then(function (data) {
console.log('data fetched:');
console.log(data);
factory.cacheTransport(transportsId, data);
return data; //this was missing
});
}
// resolve it with transport object if cached
deferred.resolve(preFetchedTransport);
return deferred.promise;
},