Deleting row with scope array leaves blank page - arrays

Trying to refresh the view with scope array after deleting a row from database I have no luck:
$scope.remove = function() {
var x = document.getElementById("name").textContent;
$cordovaSQLite.execute(db, 'DELETE from table WHERE name=?', [x])
.then(function(res) {
var index = $scope.listItems.indexOf(x);
$scope.listItems.splice(index, 1);
}, function(error) {
console.log(error.message);
})
};
Delete succeeds but leaves me a blank page. To refresh in my Ionic app I have to go to another page and back:
$scope.showConfirm = function () {
var x = document.getElementById("name").textContent;
var confirmPopup = $ionicPopup.confirm({
title: 'Deleting Journal Entry',
template: 'Are you sure you want to delete this item ?'
});
confirmPopup.then(function (res) {
if (res) {
var query = "DELETE FROM chocolates where name = ?";
$cordovaSQLite.execute(db, query, [x]);
$state.go($state.current, $stateParams, {reload: true, inherit: false});
console.log(x);
} else {
console.log('You are not sure');
};
});
};
This code also succeeds deleting the row but the refresh function is not working. I tried answers from alternative 1 and alternative 2.

if (res) {
$ionicPlatform.ready(function () {
$scope.chocolates = [];
var query = "DELETE FROM chocolates where name = ?";
$cordovaSQLite.execute(db, query, [x]);
$cordovaSQLite.execute(db, 'SELECT * FROM chocolates ORDER BY choc_id DESC LIMIT 1', []).then(function (res) {
if (res.rows.length > 0) {
for (var i = 0; i < res.rows.length; i++) {
$scope.chocolates.push(res.rows.item(i));
}
}
}, function (err) {
console.error(err);
});
$scope.$apply();
});
} else {
console.log('You are not sure');
};
This is the solution.

Related

call function synchronously inside an angular for each

I'm using ngCordova File Transfer plugin in an ionic project to download set of images from urls. Here is the code i'm using for that.
// Save a image file in a given directory
$scope.saveImage = function(dir,imgUrl,imageName) {
var url = imgUrl;
var targetPath = cordova.file.dataDirectory+ dir+"/" + imageName;
var trustHosts = true;
var options = {};
// Download the image using cordovafiletransfer plugin
$cordovaFileTransfer.download(url, targetPath, options, trustHosts)
.then(function(result) {
$scope.loadedCount ++;
$ionicLoading.show({template : "<ion-spinner class='spinner-energized'></ion-spinner><p> Downloading pages : "+ $scope.loadedCount+" of "+ $scope.pages.length+ "</p><p>Please wait...</p><p><button class=\"button button-block button-positive\">continue in background</button></p>"});
if($scope.loadedCount == $scope.pages.length) {
$ionicLoading.hide();
$scope.showDownloadSuccessAlert = function() {
var alertPopup = $ionicPopup.alert({
title: 'Success!',
template: "Your magazine successfully downloaded. You can view it on Downloads!"
});
};
$scope.showDownloadSuccessAlert();
}
}, function(err) {
alert(JSON.stringify(err));
}, function (progress) {
if($scope.loadedCount > 80) {
}
});
};
// Download the current magazine
$scope.downloadMagazine = function() {
if($rootScope.user.user_id == undefined) {
$scope.showLoginAlert = function() {
var alertPopup = $ionicPopup.alert({
title: 'Oops!',
template: "Your must login to download magazines"
});
};
$scope.showLoginAlert();
return;
}
document.addEventListener('deviceready', function () {
var dirName = $rootScope.currentIssue.slug+'_VOL_'+$rootScope.currentIssue.vol+'_ISU_'+$rootScope.currentIssue.issue;
// First create the directory
$cordovaFile.createDir(cordova.file.dataDirectory, dirName, false)
.then(function (success) {
var count = 1;
$scope.loadedCount = 0;
angular.forEach($scope.pages, function(value, key) {
var imgName = count+".png";
$scope.saveImage(dirName,value.link,imgName); // Then save images one by one to the created directory.
count++;
});
}, function (error) {
// Directory already exists means that the magazine is already downloaded.
$scope.showDownloadedAlert = function() {
var alertPopup = $ionicPopup.alert({
title: 'Why worry!',
template: "Your have already downloaded this magazine. You can view it on downloads"
});
};
$scope.showDownloadedAlert();
});
}, false);
};
})
Problem here is that program try to download everything at once without waiting for previous one to finish. How to wait for one file to finish downloading and then start the other?
Thanks
If you want to do that automatically (you're not the first one : How can I execute array of promises in sequential order?), you could try reducing the list of address to a single Promise that will do the whole chain.
$scope.pages.reduce(function (curr,next) {
return curr.then(function(){
return $scope.saveImage(dirName, curr.link, imgName);
});
}, Promise.resolve()).then(function(result) {
$ionicLoading.show({template : "<ion-spinner class='spinner-energized'></ion-spinner><p> Downloading pages : "+ $scope.loadedCount+" of "+ $scope.pages.length+ "</p><p>Please wait...</p><p><button class=\"button button-block button-positive\">continue in background</button></p>"});
if($scope.loadedCount == $scope.pages.length) {
$ionicLoading.hide();
$scope.showDownloadSuccessAlert = function() {
var alertPopup = $ionicPopup.alert({
title: 'Success!',
template: "Your magazine successfully downloaded. You can view it on Downloads!"
});
};
$scope.showDownloadSuccessAlert();
}
});
And don't forget to make your saveImage async which returns a promise.
UPDATE:
You will need to remove the then logic from your save method and return the download method call:
return $cordovaFileTransfer.download(url, targetPath, options, trustHosts).promise;
Then you can put your download handler into Promise.resolve()).then. See above.
There's no other way other than chaining your promises. Here's an example:
angular.module('app', [])
.service('fakeDownloadService', function($timeout) {
this.download = (file) => $timeout(() => file, 100);
return this;
})
.run(function($rootScope, $q, fakeDownloadService) {
var listOfFiles = [];
for (var i = 0; i < 10; i++)
listOfFiles.push('file' + i);
$rootScope.log = [];
$rootScope.download = () => {
listOfFiles
.reduce((prev, curr) => {
return prev.then((result) => {
if(result)
$rootScope.log.push(result + ' downloaded');
return fakeDownloadService.download(curr);
});
}, $q.resolve())
.then(() => $rootScope.log.push('all done'));
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.9/angular.min.js"></script>
<div ng-app="app">
<button ng-click="download()">Start</button>
<div>Log:</div>
<ul>
<li ng-repeat="entry in log track by $index">
{{entry}}
</li>
</ul>
</div>

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

How to lazy load Firebase data using ionic infinite scroll

I have a set of data within a users profile which holds the UID of all the posts they have liked. I am using a data snapshot to show these images within the users profile, but I would like to load it lazy or use ionic infinite scroll to load more items as the user scrolls.
I tried by making two duplicate snap shots. First one intialises the feed upon the page loading, and then I wanted the second one to load by using ionic infinite scroller.
But it doesn't work.
Does anyone know of a better way to do this?
service.js
getWardrobeFeed: function(){
var defered = $q.defer();
var user = User.getLoggedInUser();
var wardrobeKeyRef = new Firebase(FIREBASE_URL + '/userProfile/' + user.uid + '/wardrobe');
wardrobeKeyRef.orderByKey().limitToLast(2).on('child_added', function (snap) {
var imageId = snap.key();
userImageRef.child(imageId).on('value', function (snap) {
$timeout(function () {
if (snap.val() === null) {
delete userWardrobeFeed[imageId];
} else {
userWardrobeFeed[imageId] = snap.val();
}
});
});
});
wardrobeKeyRef.orderByKey().limitToLast(2).on('child_removed', function (snap) {
var imageId = snap.key();
$timeout(function () {
delete userWardrobeFeed[imageId];
});
});
defered.resolve(userWardrobeFeed);
return defered.promise;
},
getWardrobeItems: function(){
var defered = $q.defer();
var user = User.getLoggedInUser();
var wardrobeKeyRef = new Firebase(FIREBASE_URL + '/userProfile/' + user.uid + '/wardrobe');
wardrobeKeyRef.orderByKey().limitToFirst(2).on('child_added', function (snap) {
var imageId = snap.key();
userImageRef.child(imageId).on('value', function (snap) {
$timeout(function () {
if (snap.val() === null) {
delete userWardrobe[imageId];
} else {
userWardrobe[imageId] = snap.val();
}
});
});
});
wardrobeKeyRef.orderByKey().on('child_removed', function (snap) {
var imageId = snap.key();
$timeout(function () {
delete userWardrobe[imageId];
});
});
defered.resolve(userWardrobe);
return defered.promise;
},
controller.js
$scope.userWardrobe = [];
Service.getWardrobeFeed().then(function(items){
$scope.userWardrobe = items;
});
$scope.loadMore = function() {
Service.getWardrobeItems().then(function(items){
$scope.userWardrobe = $scope.userWardrobe.concat(items);
$scope.$broadcast('scroll.infiniteScrollComplete');
});
};

Reload View and Controller in Ionic Framework

I am building up an mobile application using Ionic Framework and Cordova Sqlite. I am displaying the data from the sqlite database in an ionic list. Each of the ionic list item has a button to delete the corresponding item from the database. On the click of the button, the data gets deleted from the database, but it continues to appear in the ionic list, until I go back to some other view and come back to it. I need to refresh the view immediately and remove that item from the list also. Also, all my SQL codes are in controller, so I also need to reload the controller, it seems.
app.js
.state('app.cart', {
url: '/cart',
views: {
'menuContent': {
cache: false,
templateUrl: 'templates/cart.html',
controller: 'NFController'
}
}
})
controller.js
.controller('NFController', ['$scope', '$cordovaSQLite','$cordovaToast','$state','$stateParams', function($scope, $cordovaSQLite, $cordovaToast, $state,$stateParams) {
$scope.listItems= [];
$cordovaSQLite.execute(db, 'SELECT * FROM cart ORDER BY id DESC')
.then(
function(res) {
$scope.cartTotal=0;
$scope.crtPP=0;
if (res.rows.length > 0) {
for (var i=0; i<res.rows.length; i++) {
**$scope.crtPP=res.rows.item(i).productPrice*res.rows.item(i).productQuantity;
$scope.cartTotal=$scope.cartTotal+$scope.crtPP;
$scope.CProductName=res.rows.item(i).productName;
$scope.listItems.push(res.rows.item(i));**
}
}
else{
$scope.status = "No Products in the Cart";
}
},
function(error) {
$scope.statusMessage = "Error on loading: " + error.message;
}
);
$scope.remove = function(id) {
$cordovaSQLite.execute(db, 'DELETE from cart WHERE id=?', [id])
.then(function(res) {
//$state.go($state.current, {}, {reload: true});
var current = $state.current;
var params = angular.copy($stateParams);
$state.transitionTo(current, params, { reload: true, inherit: true, notify: true });
$cordovaToast.show('Removed from Cart','short','bottom');
}, function(error) {
console.log(error.message);
})
}
}])
remove() is called on the button click.
Updated Code :
.controller('NFController', ['$scope', '$cordovaSQLite','$cordovaToast', function($scope, $cordovaSQLite, $cordovaToast) {
$scope.listItems= [];
$cordovaSQLite.execute(db, 'SELECT * FROM cart ORDER BY id DESC')
.then(
function(res) {
$scope.cartTotal=0;
$scope.crtPP=0;
if (res.rows.length > 0) {
for (var i=0; i<res.rows.length; i++) {
$scope.listItems.push(res.rows.item(i));
}
cartTotal(); //cartTotal() called initially when the controller loads
console.log("Cart Total : " + $scope.cartTotal);
}
else{
console.log("####console######## NO results found #######"+"Table record #: ");
}
},
function(error) {
}
);
$scope.remove = function(id) {
$cordovaSQLite.execute(db, 'DELETE from cart WHERE id=?', [id])
.then(function(res) {
var index=$scope.listItems.indexOf(id)
$scope.listItems.splice(index,1);
cartTotal(); //CartTotal() called second time
$cordovaToast.show('Removed from Cart','short','bottom');
}, function(error) {
console.log(error.message);
})
console.log(id);
}
function cartTotal()
{
angular.forEach($scope.listItems, function(item, key) {
$scope.crtPP = item.productPrice * item.productQuantity;
$scope.cartTotal += $scope.crtPP;
console.log($scope.cartTotal);
});
}
}])
When you execute the delete in your
$scope.remove = function(id) {
...
}
you don't need to the reload of the view. You can easily remove all this:
var current = $state.current;
var params = angular.copy($stateParams);
$state.transitionTo(current, params, { reload: true, inherit: true, notify: true });
you array of items $scope.listItems= []; should be bound to the view so you simply have to remove the item from the array or reload it and your view will update automatically.
$scope.remove = function(id) {
$cordovaSQLite.execute(db, 'DELETE from cart WHERE id=?', [id])
.then(function(res) {
$scope.listItems = <find the id in the list and remove it>;
$cordovaToast.show('Removed from Cart','short','bottom');
}, function(error) {
console.log(error.message);
})
}
instead of passing the id only to your $scope.remove method you can pass the whole item and use it to find the element in the array so it can be removed:
$scope.remove = function(item) {
$cordovaSQLite.execute(db, 'DELETE from cart WHERE id=?', [item.id])
.then(function(res) {
var index = $scope.listItems.indexOf(item);
$scope.listItems.splice(index, 1);
$cordovaToast.show('Removed from Cart','short','bottom');
}, function(error) {
console.log(error.message);
})
}
and your HTML:
<a class="btn" ng-click="remove(item)">Delete</a>
UPDATE:
Regarding the question in your comments, I would calculate the total using the array $scope.listItems.
I guess you have defined a property in your scope:
$scope.cartTotal = 0;
I would add a function:
function calculateCartTotal()
{
angular.forEach($scope.listItems, function(item, key) {
$scope.cartTotal += item.amount;
});
}
PS: item.amount or whatever your value is.
and recalculate after the splice:
$scope.remove = function(item) {
$cordovaSQLite.execute(db, 'DELETE from cart WHERE id=?', [item.id])
.then(function(res) {
var index = $scope.listItems.indexOf(item);
$scope.listItems.splice(index, 1);
calculateCartTotal();
$cordovaToast.show('Removed from Cart','short','bottom');
}, function(error) {
console.log(error.message);
})
}
If you cannot do that cause you don't have a value item.amount (or similar) you can always re-execute the query in that function and feed $scope.cartTotal.
UPDATE:
function cartTotal()
{
$scope.cartTotal = 0;
angular.forEach($scope.listItems, function(item, key) {
$scope.crtPP = item.productPrice * item.productQuantity;
$scope.cartTotal += $scope.crtPP;
console.log($scope.cartTotal);
});
}

Resources