Foreach loop in AngularJS and $http.get - angularjs

I have problem with my AngularJS function. Data from first forEach is retrieved with $http.get and in second forEach, $scope.products isn't defined yet. I know that $http.get() is an asynchronous request and this is the point... But how to rewrite this function to work fine ?
$scope.getAll = function () {
var cookies = $cookies.getAll();
$scope.products = [];
var i = 0;
angular.forEach(cookies, function (v, k) {
console.log("important1:" + $scope.products);
console.log("key: " + k + ", value: " + v);
ProductsService.retrieve(k).then(function(response) {
$scope.products = $scope.products.concat(response.data);
$scope.products[i].quantity = v;
i++;
}, function (error) {
console.log('error');
});
});
console.log("important2:" + $scope.products);
angular.forEach($scope.products, function(value, key) {
$scope.total = value.quantity*value.price + $scope.total;
console.log("Quantiy: " + value.quantity);
console.log("Price: " + value.price);
});
console.log($scope.products);
console.log($scope.total);
};

I suggest that you use the $q.all().
More specifically, you would do:
$q.all([p1, p2, p3...]).then(function() {
// your code to be executed after all the promises have competed.
})
where p1, p2, ... are the promises corresponding to each of your ProductsService.retrieve(k).

Build up a list of service calls then call $q.all. Inside the then function you can put your second loop.
var listOfServiceCalls = [];
//Add to list of service calls
$q.all(listOfServiceCalls)
.then(function () {
//All calls completed THEN
angular.forEach($scope.products, function(value, key) {
$scope.total = value.quantity*value.price + $scope.total;
});
});

Related

how can I use a function which is located inside a service?

What I am trying to do is to use function globally throughout controllers.
The problem is when I want to use the function I defined inside the service in the first function. It shows an error that it cannot find a function. I tried without this keyword but it's not working. I can go to all function when I tried in other controllers, which is a good sign that I can use this service globally.
In short, I want to use all function inside first function.
app.factory("UserService", function() {
var users = ["Peter", "Daniel", "Nina"];
return {
all: function() {
return users;
},
first: function() {
var users = this.all();
return users[0];
}
};
});
The code above was an example that I made and real code appears like this.
controller
angular.module("app").requires.push("app.region");
I put the region to app so I can use the service.
After that I made a controller like this
.controller("regionCreateController", ["$scope", "phoneMaskService", function ($scope, phoneMaskService) {
$scope.createClicked = function (data) {
data = phoneMaskService.putMaskOnRegion(data);
console.log(data);
};
}
When I put phoneMaskService which is the service I made in the app.js and it fails.
This is the error I am getting
angular.js:14110 ReferenceError: removeAllLetters is not defined
This is the actual code making errors.
.factory("phoneMaskService", [function () {
var returnMethod = {
removeAllLetters: removeAllLetters,
putMaskOn: putMaskOn,
putMaskOnRegion: putMaskOnRegion
};
return returnMethod;
function removeAllLetters(value) {
var val = value.replace(/\D+/g, '').replace('\-', '');
return val;
}
function putMaskOn(value) {
console.log(value);
value = this.removeAllLetters(value);
console.log(value);
var isMobile = parseInt(value.charAt(1)) == 2;
if (isMobile) {
var x = value.replace(/\D/g, '').substring(0, 14).match(/(\d{3})(\d{3})(\d{3,})/);
x = ' ( ' + x[1] + ' ) ' + x[2] + ' - ' + x[3];
return x;
} else {
var x = value.replace(/\D/g, '').substring(0, 14).match(/(\d{2})(\d{3})(\d{3,})/);
x = ' ( ' + x[1] + ' ) ' + x[2] + ' - ' + x[3];
return x;
}
}
function putMaskOnRegion(object) {
angular.forEach(object, function (value, key) {
if (key == "contactNumberPhone") {
var testvalue = this.removeAllLetters(value);
console.log(this);
console.log("test value" + testvalue);
object[key] = this.removeAllLetters(value);
}
});
return object;
}
}])
The error happens the line here and says removeallletters are undefined
var testvalue = this.removeAllLetters(value);
One approach to avoid binding problems is to declare the functions inside the factory:
app.factory("UserService", function() {
var users = ["Peter", "Daniel", "Nina"];
return { all: all, first: first };
function all() {
return users;
}
function first() {
var users = all();
return users[0];
}
});
I use the follwoing when declaring factories, which
creates an object within the factory declaration, binds methods to it and returns is as the the factory object.This might work in your case.
app.factory("UserService", function() {
var services = {};
services.users = ["Peter", "Daniel", "Nina"];
services.all = function() {
return services.users;
}
services.first = function() {
return services.all()[0];
}
return services;
});

Adding a function to a factory (singleton) inside a controller

I have a factory called search.
I have many controllers called SearchCampaignController, SearchQuotaController and so on.
The factory search is a singleton object to which these controllers add function implementations to.
The problem is, a parent controller called SearchController must call a function inside the factory search which is not yet implemented because the child controllers execute after the parent controller executes.
It seems like I'm doing something wrong.
angular
.module('app.search')
.factory('search', search);
search.$inject = ['$http', '$window', '$state', 'CONF', '$mdToast', 'USER_ROLES', 'USER_MODULES'];
function search($http, $window, $state, CONF, $mdToast, USER_ROLES, USER_MODULES) {
var searchInfo;
function updateQueryString(params) {
$state.go(
'search',
{
searchType: params.searchType,
quotaId: params.quotaId,
campaignName: params.campaignName,
templateName: params.templateName,
passId: params.passId,
certId: params.certId
},
{notify: false}
)
}
function getQuotaById(params) {
var reqPath = CONF.apiPath + 'core/quotas/' + params.quotaId;
return $http.get(reqPath);
}
function getCampaignById(params) {
var reqPath = CONF.apiPath + 'core/quotas/' + params.quotaId + '/campaigns/' + params.campaignName;
return $http.get(reqPath);
}
function queryCampaigns(params) {
var reqPath = CONF.apiPath + 'core/quotas/' + params.quotaId + '/campaigns';
return $http.get(reqPath);
}
function getTemplateById(params) {
var reqPath = CONF.apiPath + 'core/quotas/' + params.quotaId + '/campaigns/' + params.templateName;
return $http.get(reqPath);
}
function queryTemplates(params) {
var reqPath = CONF.apiPath + 'core/campaigns/' + params.campaignName + '/templates';
return $http.get(reqPath);
}
function getPassById(params) {
var reqPath = CONF.apiPath + 'core/passes/' + params.passId;
return $http.get(reqPath);
}
function getPassbookById(params) {
var reqPath = CONF.apiPath + 'core/passbookCert/' + params.certId;
return $http.get(reqPath);
}
function queryPassbookCerts(params) {
var reqPath = CONF.apiPath + 'core/quotas/' + params.quotaId + '/passbookCerts';
return $http.get(reqPath);
}
//Global search logic
//NEED TO RE-FACTOR
function searchMasterFunction(params, obj) {
if(params.searchType){
search.changeSearchType(params.searchType);
}
updateQueryString(params);
if(params.quotaId){
search.getQuotaAndDisplayResult(params);
// search.getPassbookCertsAndDisplayResult(params);
search.updateQuotaIdInTemplateTab(params); //special - needs re-visit
}
if(params.quotaId && !params.campaignName){
search.getCampaignsAndDisplayResult(params);
}
if(params.quotaId && params.campaignName && params.templateName){
search.getCampaignAndDisplayResult(params);
search.getTemplateAndDisplayResult(params);
}else if(params.quotaId && params.campaignName){
search.getCampaignAndDisplayResult(params);
search.getTemplatesAndDisplayResult(params);
}else if(params.quotaId && params.templateName){
search.getTemplateAndDisplayResult(params);
}
if(params.campaignName){
search.getTemplatesAndDisplayResult(params);
}
if(params.passId){
search.getPassAndDisplayResult(params);
}
//getPassbookById
}
var search = {
searchInfo: searchInfo,
searchMasterFunction: searchMasterFunction,
getQuotaById: getQuotaById,
getCampaignById: getCampaignById,
queryCampaigns: queryCampaigns,
getTemplateById: getTemplateById,
queryTemplates: queryTemplates,
getPassById: getPassById,
getPassbookById: getPassbookById,
queryPassbookCerts: queryPassbookCerts
};
return search;
}
And this is my parent controller which should call the searchMasterFunction inside the factory search so that when there are values in the query string, it automatically populates any search results according to the logic inside the search factory.
angular
.module('app.search')
.controller('SearchController', SearchController);
SearchController.$inject = ['$state', 'search', '$scope', '$log'];
function SearchController($state, search, $scope, $log){
$log.warn("Executing SearchController");
var vm = this;
vm.searchType = $state.params.searchType; //re-visit
vm.tabs = ['quota', 'campaign', 'template', 'pass', 'cert'];
vm.changeSearchTypeOnTabClick = changeSearchTypeOnTabClick;
search.changeSearchType = changeSearchType;
function changeSearchTypeOnTabClick(searchType) {
$state.go('search', {searchType: searchType}, {notify: false});
}
function changeSearchType(searchType) {
vm.searchType = searchType;
}
// this function call is what is causing the problem
// search.searchMasterFunction($state.params);
}
The following is one of my child controllers which implement functions such as search.getQuotaAndDisplayResult.
angular
.module('app.search')
.controller('SearchQuotaController', SearchQuotaController);
SearchQuotaController.$inject = ['search', '$scope', '$log'];
function SearchQuotaController(search, $scope, $log){
$log.info("Executing SearchQuotaController");
var vm = this;
vm.searchInfo;
vm.quota;
vm.searchBtnClick = searchBtnClick;
search.getQuotaAndDisplayResult = getQuotaAndDisplayResult; //THIS LINE IS WHAT NEEDS ATTENTION. I'm adding a function into the `search` factory.
function searchBtnClick(params){
search.searchMasterFunction(params);
};
function getQuotaAndDisplayResult(params) {
vm.searchInfo = params; //update fields in the Quota view
search.getQuotaById(params).then(function(quota){
vm.quota = quota.data; //update the quota object in the view
});
};
}
So the problem is that SearchQuotaController runs AFTER SearchController and therefore if I try to call search.searchMasterFunction in SearchController, it will not be able to execute properly since search.searchMasterFunction relies on the child controller to execute in order for the function implementation to be complete.
Any help would be greatly appreciated. I've already considered the $broadcast method but it seems like a hack and not a real solution.
PS. The reason why I'm adding functions from child controllers into the search factory is because child controllers have access to their local $scope.

Ionic/Angular concurrent SQLite connections and writes failing

I'm fairly new to both Ionic and Angular. I'm trying to write to a SQLite database and it's intermittently successful. When I do a for loop and insert records rapidly, some succeed and some fail (without apparent error). The query execution uses promises, so multiple queries may be trying to execute concurrently. It seems that this causes a synchronization issue in SQLite - or the SQLite plugin. I've tried opening a new DB connection with every execute(), reopening the existing connection on every execute(), and I've also tried opening a connection globally in app.js once and reusing that connection. They all seem to behave the same.
A custom 'dbQuery' function is used to build queries and is chainable. The idea is that any place in my app with access to the DB service can execute a query and expect the results to flow into an "out" variable like:
var my_query_results = [];
DB.begin().select("*","table1").execute(my_query_results).then(function() {
console.log("Query complete");
});
That much works, but the problem comes from writes:
var records = [
{id:1, name:"Bob"},
{id:2, name:"John"},
{id:3, name:"Jim"},
];
for (var i = 0; i < records.length; i++) {
var obj = records[i];
var result = [];
DB.begin().debug(true).insert("table1", "(id,name)", "("+obj.id+","+ obj.name+")").execute(result).then(function () {
console.log("Inserted record", JSON.stringify(obj));
});
}
Sometimes it fails without any logged or apparent error, sometimes it succeeds. If I perform the inserts slowly over time, it seems to work without issue.
app.js
var db;
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services'])
.run(function ($ionicPlatform, $cordovaSQLite, appConfig, $q) {
$ionicPlatform.ready(function () {
db = $cordovaSQLite.openDB({
name: appConfig.sqlite_db,
location: appConfig.sqlite_db_location
});
dbQuery = function () {
this.bDebug = false;
this.query = "";
this.result = [];
this.params = [];
this.debug = function (value) {
this.bDebug = (value === true ? true : false);
return this;
};
this.rawQuery = function (query) {
this.query = query;
return this;
};
this.insert = function (table, fields, values) {
this.query = "INSERT INTO '" + table + "' (" + fields + ") VALUES (" + values + ")";
return this;
};
this.select = function (fields, table) {
this.query = "SELECT " + fields + " FROM " + table;
return this;
};
this.delete = function (query) {
this.query = "DELETE FROM " + query;
return this;
};
this.where = function (column, expression, value) {
expression = expression || "=";
this.query += " WHERE `" + column + "` " + expression + " ? ";
this.params[this.params.length] = value;
return this;
};
this.and = function (column, expression, value) {
expression = expression || "=";
this.query += " AND '" + column + "' " + expression + " ? ";
this.params[this.params.length] = value;
return this;
};
this.execute = function (out_var) {
var self = this;
this.result = out_var;
if (this.bDebug) {
console.log("Compiled query is", this.query);
}
var deferred = $q.defer();
db.open(function () {
console.log("Opened");
}, function () {
console.log("Failed");
});
//actually execute the query
$cordovaSQLite.execute(db, this.query, this.params).then(
function (res) {
for (var i = 0; i < res.rows.length; i++) {
self.result.push(res.rows.item(i));
console.log("Added row to set", JSON.stringify(res.rows.item(i)));
}
if (res.rows.length == 0 && self.bDebug === true) {
console.log("No results found ");
}
deferred.resolve();
}, function (err) {
console.error(JSON.stringify(err), this.query);
deferred.reject();
});
return deferred.promise;
}
services.js
.factory('DB', function ($ionicPlatform) {
return {
begin: function () {
return new dbQuery();
}
}
})
.factory('DbBootstrap', function ($cordovaSQLite, appConfig, $q, $state, DB) {
return {
wipe: function () {
DB.begin().rawQuery("DELETE FROM table1").execute().then(function () {
console.log("Purged records");
});
},
init: function () {
var result = []; //out variable
DB.begin().rawQuery("CREATE TABLE IF NOT EXISTS table1 (id integer primary key, name text)").execute(result).then(function () {
console.log("Schema create returned", JSON.stringify(result));
});
var records = [
{
id: 1, name:'Jim'
...
},
{
id: 2, name:'Bob'
...
},
{
id: 3, name:'John'
...
}
];
for (var i = 0; i < records.length; i++) {
var obj = records[i];
var result = [];
DB.begin().debug(true).insert("table1", "(id,name)", "(obj.id, obj.name).execute(result).then(function () {
console.log("Inserted record", JSON.stringify(obj));
});
}
}
})
I'm sure I'm missing something fundamental about angular, promises, and sqlite locking. If anyone has advice I'd really appreciate it.
I resolved this following the excellent advice here - Angular/Ionic and async SQLite - ensuring data factory initialised before return
The key issue being that I needed to wrap all my DB operations in promises and use them for orderly initialization and callbacks.
.factory('DB', function ($q, $cordovaSQLite, appConfig) {
//private variables
var db_;
// private methods - all return promises
var openDB_ = function (dbName, location) {
var q = $q.defer();
try {
db_ = $cordovaSQLite.openDB({
name: dbName,
location: location
});
q.resolve(db_);
} catch (e) {
q.reject("Exception thrown while opening DB " + JSON.stringify(e));
}
return q.promise;
};
var performQuery_ = function (query, params, out) {
var q = $q.defer();
params = params || [];
out = out || [];
//open the DB
openDB_(appConfig.sqlite_db, appConfig.sqlite_db_location)
.then(function (db) {
//then execute the query
$cordovaSQLite.execute(db, query, params).then(function (res) {
//then add the records to the out param
console.log("Query executed", JSON.stringify(query));
for (var i = 0; i < res.rows.length; i++) {
out.push(res.rows.item(i));
console.log("Added row to set", JSON.stringify(res.rows.item(i)));
}
if (res.rows.length == 0 && self.bDebug === true) {
console.log("No results found ");
}
}, function (err) {
console.log("Query failed", JSON.stringify(query));
q.reject();
});
db_.open(function () {
q.resolve("DB Opened")
}, function () {
q.reject("Failed to open DB");
});
}, function (err) {
console.log(JSON.stringify(err), this.query);
q.reject(err);
});
return q.promise;
};
// public methods
var execute = function (query, params, out) {
var q = $q.defer();
performQuery_(query, params, out).then(function () {
q.resolve([query, params]);
}, function (err) {
q.reject([query, params, err]);
});
return q.promise;
};
return {
execute: execute
};
})

AngularJS/Ionic promises - sometimes page loads forever

I am using promises in my controller, and most of the times it works well. But sometimes it just loads forever and the WordPress.getAllCategories() function does not even get called.
This is my controller:
var mod = angular.module('app.controllers.home', []);
mod.controller('HomeCtrl', function ($scope, $q, $sce, $ionicPlatform, WordPress, Loading) {
console.log('HomeCtrl init');
$scope.$on('$ionicView.enter', function () {
Loading.show();
WordPress.getAllCategories()
.then(function (cats) {
console.info(angular.toJson(cats));
console.info('cats ^');
$q.all(cats.data.map(function (cat) {
var d = $q.defer();
console.error(cat.name);
WordPress.getLatestPostOfCategory(cat.id)
.then(function (post) {
console.debug(post.data.title.rendered);
WordPress.getMediaById(post.data.featured_media)
.then(function (media) {
console.log(media.data.source_url);
cat.firstPost = {};
cat.firstPost.id = post.data.id;
cat.firstPost.title = post.data.title.rendered;
cat.firstPost.content = post.data.content.rendered;
if (cat.firstPost.title.length > 50) {
cat.firstPost.title = cat.firstPost.title + '...';
}
if (cat.firstPost.content.length > 70) {
cat.firstPost.content = cat.firstPost.content.substr(0, 60) + '...';
}
cat.firstPost.thumbnail = media.data.source_url;
d.resolve(cat);
}, function (err) {
console.error(angular.toJson(err));
});
});
return d.promise;
})).then(function (cats) {
console.log('Loaded all articles and for main page.');
$scope.homeCategories = cats;
Loading.hide();
});
});
});
});
Is there anything wrong in my controller?
P.S. I also debug all the WordPress service functions and they work just fine, and provide the needed data.
EDIT:
Sometimes when it loads forever, I see the console.error(cat.name); debug message only logs 3 messages. But still proceeds to the next function...
This is how I solved it, by Bergi's advice.
Source for help: Promise anti pattern by Gorgi Kosev (bluebird)
var categories = [];
function sort() {
return WordPress.getAllCategories()
.then(function (cats) {
console.log('thens');
return $q.all(cats.data.map(function (cat) {
console.info('cat: ' + cat.name);
var category = {};
category.name = cat.name;
return WordPress.getLatestPostOfCategory(cat.id)
.then(function (post) {
var post = post.data;
category.post = {};
category.post.id = post.id;
category.post.title = post.title.rendered;
category.post.content = post.content.rendered;
console.log('ID: ' + category.post.id + ', title: ' + category.post.title);
return WordPress.getMediaById(post.featured_media);
}).then(function (media) {
category.post.thumbnail = media.data.source_url;
categories.push(category);
console.log('Pushed category "' + category.name + '"');
});
}));
}, function (err) {
console.error('ERR1');
console.error(angular.toJson(err));
});
}
sort()
.then(function () {
console.info('LOADED ALL CATEGORIES');
$scope.categories = categories;
}, function (err) {
console.error('err:' + angular.toJson(err));
});

Key Inside For Loop Is Same inside then function

$scope.expandPackage = function (node, expanded) {
$scope.expansion=expanded;
var promises = [];
//Check that expand command was clicked and the node's children were not already fetched
if (expanded && !node.childrenFetched) {
//console.log("Call to fetch children");
//node.children = fetchChildren(node.name);
var promise=getPackageHierarchyByPackageId(node.packageId).then(function(){
if( $scope.packageEnabled) {
var featureinstancePromise=FeatureInstanceService.getfeatureInstanceForOrgUnitId(myRow.orgUnitId);
featureinstancePromise.then(function (featureInstance) {
featureInstanceList=featureInstance;
}).then(function(){
prepareListOfFeatureInstance(featureInstanceList,featureList);
for (var key in featureAndFeatInstanceIdMap) {
if (featureAndFeatInstanceIdMap.hasOwnProperty(key)) {
console.log(key + " -> " + featureAndFeatInstanceIdMap[key]);
var enablementPromise=FeatureInstanceService.getEnablementInfo(key);
promises.push(enablementPromise);
enablementPromise.then(function(enablementInfoSuccessResponse){
$scope.featureAndEnablementInfo[featureAndFeatInstanceIdMap[key].displayName]=enablementInfoSuccessResponse;
});
}
}
$q.all(promises).then(function(){
$scope.featureList=featureList;
});
});
}
});
node.childrenFetched = true;
}
};
this.getEnablementInfo = function(featureInstanceId) {
var defer = $q.defer();
$http.get(baseUrl + 'featureInstances/'+featureInstanceId +'/enablementInfo') .
success(function(data, status, headers, config) {
defer.resolve(data);
}).error(function(data, status, headers, config) {
console.log("Error message: " + data.message);
defer.reject(data);
});
return defer.promise;
};
while executing then function it always return same key but the enablementInfoSuccessResponse is fine. My question is how would I know which response is for which key ? Why key is always same ? How I can get rid out of this problem ?
The key is always the same because it is scoped to the then function called after resolving featureinstancePromise. Since promises exist for handling asynchronous code, the for loop keeps on iterating while not caring about when the promise will resolve. This is great but you cannot rely on key being the same since it is updated on every loop.
There are several ways to solve this but the easiest is to move the logic in your loop into a function and pass in the key. That way key will be scoped to that function and won't change underneath you.
function loopWork(key) {
if (featureAndFeatInstanceIdMap.hasOwnProperty(key)) {
console.log(key + " -> " + featureAndFeatInstanceIdMap[key]);
var enablementPromise=FeatureInstanceService.getEnablementInfo(key);
promises.push(enablementPromise);
enablementPromise.then(function(enablementInfoSuccessResponse){
$scope.featureAndEnablementInfo[featureAndFeatInstanceIdMap[key].displayName]=enablementInfoSuccessResponse;
});
}
}
prepareListOfFeatureInstance(featureInstanceList,featureList);
for (var key in featureAndFeatInstanceIdMap) {
loopWork(key);
}

Resources