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.
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));
});
I have a simple query that I am struggling with:
.factory('Config', function($http, DB) {
var self = this;
self.setValue = function(key,value) {
console.log('setValue(value)', value);
return DB.query("UPDATE config SET value = '"+value+"' WHERE key = '"+key+"'")
.then(function(result){
return DB.fetchAll(result);
});
}
self.getValue = function(key) {
return DB.query("SELECT value FROM config WHERE key = '"+key+"'")
.then(function(result){
return DB.fetchOne(result);
});
};
return self;
})
with the following code in controller.js under the heading:
.factory('DB', function($q, DB_CONFIG) {
var self = this;
self.db = null;
(I took the init part of the function away for the sake of simplicity. Also DB is working well when inserting, getting and updating data.)
self.fetchOne = function(result) {
var output = null;
output = angular.copy(result.rows.item(0));
return output;
};
self.query = function (sql, bindings) {
bindings = typeof bindings !== 'undefined' ? bindings : [];
var deferred = $q.defer();
self.db.transaction(function (transaction) {
transaction.executeSql(sql, bindings, function (transaction, result) {
deferred.resolve(result);
}, function (transaction, error) {
deferred.reject(error);
});
});
return deferred.promise;
};
self.fetchAll = function (result) {
var output = [];
for (var i = 0; i < result.rows.length; i++) {
output.push(result.rows.item(i));
}
return output;
};
Called like so:
$scope.config.recordar = Config.getValue("recordar");
Doing a console.log returns:
I am struggling to access the value: "true" which is highlighted in BLUE in the above image.
Any leads?
$scope.config.recordar = Config.getValue("recordar");
Does not work (ala JS). It has to be called like so:
Config.getValue("recordar").then(function(data) {
$scope.config.recordar
});
I assume that you shall change your function declaration from :
self.setValue = function(key,value) {
console.log('setValue(value)', value);
return DB.query("UPDATE config SET value = '"+value+"' WHERE key = '"+key+"'")
.then(function(result){
return DB.fetchAll(result);
});
}
to
self.setValue = function(key,value) {
console.log('setValue(value)', value);
DB.query("UPDATE config SET value = '"+value+"' WHERE key = '"+key+"'")
.then(function(result){
return DB.fetchAll(result);
});
}
You will return the result of your promise, not your promise
I have changed "return DB..." to "DB..."
I have code that I use to check a connection to the server. My code runs every 60 seconds. Once the code has run then it creates a message and this shows up on a page. Here's what I have so far:
The code that checks:
$interval(function () {
us.isConnected().then(closeConnect, openConnect);
}, 60 * 1000);
The code that does the check
isConnected = (): ng.IPromise<any> => {
var self = this;
var deferred = this.$q.defer();
this.$http({
method: 'GET',
url: self.ac.baseUrl + '/api/Connect/Verify'
})
.success(() => {
self.connects = 0;
self.connectMessage = null;
deferred.resolve();
})
.error(() => {
if (self.connects == 0) {
self.connectMessage = "Unable to establish a connection to the server. " + retryMessage();
} else if (self.connects == 1) {
self.connectMessage = "Unable to establish a connection to the server for " + self.connects + " minute" + retryMessage();
} else {
self.connectMessage = "Unable to establish a connection to the server for " + self.connects + " minutes." + retryMessage();
}
self.connects++;
deferred.reject();
});
return deferred.promise;
};
What I would like to do is to have a simple function called retryMessage() that will allow me to give a message like this:
Unable to establish a connection to the server for 164 minutes.
Connection will be retried in 59 seconds.
Unable to establish a connection to the server for 164 minutes.
Connection will be retried in 58 seconds.
Unable to establish a connection to the server for 164 minutes.
Connection will be retried in 57 seconds.
...
Unable to establish a connection to the server for 164 minutes.
Connection will be retried in 1 seconds.
Unable to establish a connection to the server for 164 minutes.
Retrying connection now.
Unable to establish a connection to the server for 165 minutes.
Connection will be retried in 59 seconds.
With the number of seconds counting down until 0 when there will be a recheck.
Can anyone suggest a way in AngularJS that I can achieve this countdown?
One possible way of doing what you're attempting is to use $q.notify in conjunction with $interval.
Other messages can be passed with the resolve and reject. Of course, any place this is done you could just print straight to a logging service instead, but I thought it might be appropriate to return these messages with the promise behaviours (where, if you wanted, you could even instead return an object complete with status code and other data along with the message).
In the below sample, I've let the controller manage the logging and limited its output to 12 lines. I've also specified parameters for the connection testing so it attempts to connect every 60 seconds, 20 times (these params could be changed to attempt at different intervals, a different amount of times, or indefinitely). If the test is a failure, it prints the retry message(s) every second until the retry attempt:
(function() {
"use strict";
var myApp = angular.module('myApp', []);
myApp.controller('MainController', ['$scope', 'us', '$log', MainController]);
myApp.service('us', ['$interval', '$q', '$http', '$log', usService]);
/* Controller */
function MainController($scope, us, $log) {
var _data = {
connectLog: null
},
_connectMessages = [],
_MAX_LOG_LINES = 12;
$scope.data = _data;
_log("Starting connection test...");
us.testConnection(60, 20) //60 seconds between tests, 20 tests (if no max specified, could run forever...)
.then(onTestsSuccessful, onTestsFailed, onNotifying);
function onTestsSuccessful(result) {
_log(result);
// do success stuff...
}
function onTestsFailed(result) {
_log(result);
// do failed stuff...
}
function onNotifying(result) {
_log(result);
//do retrying stuff...
}
function _log(message, deferOutput) {
//$log.debug(message);
_connectMessages.push(message);
if (_MAX_LOG_LINES && _connectMessages.length > _MAX_LOG_LINES) {
_connectMessages.splice(0, _connectMessages.length - _MAX_LOG_LINES);
}
if (!deferOutput) {
_data.connectLog = _connectMessages.join('\n');
}
}
}
/* Service */
function usService($interval, $q, $http, $log) {
var _testConnectionInterval,
_testsRun;
return {
testConnection: _startTestConnection
};
function _startTestConnection(secondsBetweenTests, maxTests) {
var deferred = $q.defer(),
connectAttempts = 0;
_cancelConnectionTest();
_connectionTest().then(onConnectionTestSuccess, onConnectionTestFail); //immediately do first test
_testsRun++;
if (secondsBetweenTests > 0) {
_testConnectionInterval = $interval(
function repeatConnectionTest() {
if (maxTests && _testsRun >= maxTests) {
return _cancelConnectionTest();
}
deferred.notify("Retrying connection now.");
_connectionTest().then(onConnectionTestSuccess, onConnectionTestFail);
_testsRun++;
},
secondsBetweenTests * 1000); //start the countdown to the next
}
function onConnectionTestSuccess(result) {
connectAttempts = 0;
if ((maxTests && _testsRun >= maxTests) || !secondsBetweenTests) {
deferred.resolve("Last connection test success, " + _testsRun + " tests complete.");
} else {
deferred.notify("Connection test success.");
}
}
function onConnectionTestFail(result) {
var minutesPassed = connectAttempts * secondsBetweenTests / 60,
minutesRoundedToTwoDec = +(Math.round(minutesPassed + "e+2") + "e-2");
var connectMessage = "Unable to establish a connection to the server" + (connectAttempts === 0 ? "." : " for " + minutesRoundedToTwoDec + " minute" + (minutesPassed > 1 ? "s." : "."));
connectAttempts++;
if ((maxTests && _testsRun >= maxTests) || !secondsBetweenTests) {
deferred.reject("Last connection test failed, " + _testsRun + " tests completed.");
} else {
deferred.notify(connectMessage);
deferred.notify("Connection will be retried in " + secondsBetweenTests + " seconds.");
var retryInterval = $interval(
function retryMessage(counter) {
deferred.notify(connectMessage);
var secondsLeft = (secondsBetweenTests - 1) - counter;
deferred.notify("Connection will be retried in " + secondsLeft + " second" + (secondsLeft > 1 ? "s." : "."));
if (!secondsLeft) {
$interval.cancel(retryInterval);
retryInterval = null;
}
},
1000, (secondsBetweenTests - 1));
}
}
return deferred.promise;
}
function _connectionTest() {
var deferred = $q.defer(),
getBroken = {
method: 'GET',
url: '/api/never/gonna/give/you/up'
};
$http(getBroken)
.success(function onSuccess() {
deferred.resolve('Success!');
})
.error(function onError() {
deferred.reject('Failure!');
});
return deferred.promise;
}
function _cancelConnectionTest() {
_testsRun = 0;
if (!_testConnectionInterval) {
$log.debug("No previously running connection test to cancel.");
return;
}
$log.debug("Cancelling connection test.");
$interval.cancel(_testConnectionInterval);
_testConnectionInterval = null;
}
}
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
<div ng-app="myApp">
<pre ng-controller="MainController">{{data.connectLog}}</pre>
</div>
You can do a countdown from 60 seconds like this:
countdown(60);
function countdown(current_time){
if(current_time === 0){
//call function to connect to server
return;
}
else{
current_time--;
}
$scope.time = current_time;
$timeout(function(){countdown(current_time)}, 1000);
}
codepen
I'd recommend putting the error message in the html and hiding it with a ng-show or ng-if and then just changing the number so that you don't need to append all that text over and over again.
I don't think using a directive is necessary, you can probably do everything inside the controller. However you implemented closeConnection and openConnection you should edit those methods adding 'start' and 'stop' $interval.
Also remember that $interval takes a max number of recursions that in this case is pretty useful.
https://docs.angularjs.org/api/ng/service/$interval
function controllerFunc($scope, $interval) {
var timer;
var lastConnection = 0;
$scope.message = '';
//this is what you already have
$interval(function () {
us.isConnected().then(closeConnect, openConnect);
}, 60 * 1000);
//...
function closeConnect() {
//...
$interval.cancel(timer);
lastConnection = 0;
}
function openConnect() {
//...
timer = $interval(timerCall, 1000, 60);
lastConnection++;
}
function timerCall(times) {
$scope.message += 'Unable to establish a connection to the server for ' + lastConnection + ' minutes. ';
var retry = 60 - times;
$scope.message += 'Connection will be retried in '+ retry +' seconds';
}
}
Formatting the Message
$scope.message is a plain string in this example so you wont get any formatting but you can put it inside an ng-bind-html directive and then add any html tag to the message string.
https://docs.angularjs.org/api/ng/directive/ngBindHtml
<div ng-bind-html="message"></div>
And so changing the js
$scope.message += '<p>Connection will be retried in '+ retry +' seconds</p>';
I do have a directive for very similar purpose, you can check it here on plunker
basically it uses (1) timer & (2) refresh-state, it is also configuratble as in no of seconds for timeout, read from attrs of directive.
It does call a given function after given interval, it also destroys the #interval on $scope change.
Below is the code for it
app.directive("csAutoRefresh", ["$interval", function ($interval) {
var templateFn = function () {
var template = '<div class="text-left alert alert-success nopadding"';
template += 'style="margin-bottom: 0; margin-right: 0"> ';
template += ' <button class="btn btn-link" data-ng-click="factory.refresh.toggle()">';
template += '{{factory.refresh.refreshText()}}</button>';
template += '<span>...Refreshing upload status in ';
template += ' {{state.timer.timePending}} seconds</span>';
template += ' </div>';
return template;
};
var linkFn = function (scope) {
scope.pauseOn = scope.pauseOn === true;
scope.isprocessing = false;
scope.state = {
refresh : {
suspend : false
},
timer : {
timePending: 0,
refreshInterval : 60
}
}
function doRefresh() {
var refresh = {
pause: function () { scope.state.refresh.suspend = true; },
cont: function () { scope.state.refresh.suspend = false; },
toggle: function () { scope.state.refresh.suspend = !scope.state.refresh.suspend; },
refreshText: function () { return scope.state.refresh.suspend ? "Resume Refresh" : "Pause Refresh"; }
};
return refresh;
}
function doTimer() {
var timer = {
reset: function () { scope.state.timer.timePending = scope.state.timer.refreshInterval; },
update: function () {
if (scope.state.timer.timePending < 0) timer.reset();
scope.state.timer.timePending--;
},
done: function () { return scope.state.timer.timePending <= 0; },
force: function () { scope.state.timer.timePending = 0; }
};
return timer;
};
scope.factory = {
refresh: doRefresh(),
timer: doTimer()
};
if (angular.isDefined(scope.interval) && parseInt(scope.interval) > 0) {
scope.state.timer.refreshInterval = scope.interval;
}
scope.factory.timer.reset();
scope.$watch(function () {
return scope.state.timer.timePending;
}, function () {
if (!scope.factory.timer.done()) return;
var result = scope.$eval(scope.onTimeout);
if (angular.isObject(result) && angular.isFunction(result.then)) {
scope.isprocessing = false;
scope.factory.timer.reset();
result.finally(function () { scope.factory.timer.reset(); });
} else {
scope.isprocessing = false;
scope.factory.timer.reset();
}
});
scope.$watch('pauseOn', function () {
if (scope.pauseOn) {
scope.factory.refresh.pause();
} else {
scope.factory.timer.reset();
scope.factory.refresh.cont();
}
});
var updateTimer = function () {
if (scope.isprocessing) return;
if (scope.state.refresh.suspend) return;
scope.factory.timer.update();
};
var interval = $interval(updateTimer, 1000);
scope.$on('$destroy', function () {
$interval.cancel(interval);
});
};
return {
restrict: 'E',
scope: { onTimeout: '&', pauseOn: '=', interval: '#' },
template: templateFn,
link: linkFn,
};
}]);
$interval(function () {
us.isConnected().then(closeConnect, openConnect);
}, 1000);
Not
$interval(function () {
us.isConnected().then(closeConnect, openConnect);
}, 20 * 1000);
The latter does the check every 20 secs (20*1000ms) so unless the actual checking code is buggy, it should run every second.
So depending on how you are looking to output the details to the user, you'd probably be best making this some type of directive and controlling everything within that.
The key to doing what you want to do is to use the $interval service, which returns an id:
$scope.intervalId = $interval(retryMessage, 1000);
You can then cancel depending on whatever conditions your set.
I made a plnkr demonstrating what you're looking to accomplish:
http://plnkr.co/edit/RmADu1aiOUO5o4k4pnqE?p=preview
I wrote this timer recently from which you may be able to take some logic - Plunker. It counts up and it can end at a set time
JS
(function() {
'use strict';
var angularTimerApp = angular.module('angularTimerApp', []);
angularTimerApp.directive("angularTimer", function() {
return {
restrict: "E",
templateUrl: "angular-timer.html",
scope: {endTime: "#"},
controllerAs: "at",
bindToController: true,
controller: ["$scope", "$interval", function ($scope, $interval) {
var at = this;
at.secondsInAYear = 31536000;
at.secondsInADay = 86400;
at.secondsInAnHour = 3600;
at.secondsInAMinute = 60;
at.endTimeValue = null;
$scope.$watch("at.endTime", function(newValue) {
if (angular.isDefined(newValue)) {
at.endTimeValue = parseInt(newValue); // No test for int
}
});
at.getTimeDisplay = function(seconds) {
var hoursTxt = Math.floor(((seconds % at.secondsInAYear) % at.secondsInADay) / at.secondsInAnHour);
var minutesTxt = Math.floor((((seconds % at.secondsInAYear) % at.secondsInADay) % at.secondsInAnHour) / at.secondsInAMinute);
var secondsTxt = (((seconds % at.secondsInAYear) % at.secondsInADay) % at.secondsInAnHour) % at.secondsInAMinute;
return ("Hours: " + hoursTxt + ", Minutes: " + minutesTxt + ", Seconds: " + secondsTxt);
};
at.reset = function () {
at.timeOffset = 0;
at.timeDisplay = at.getTimeDisplay(0);
};
at.reset();
at.stop = function () {
$interval.cancel(at.timer);
at.timeOffset = at.time;
};
at.start = function() {
at.timeStart = (new Date()).getTime();
at.timer = $interval(function() {
at.time = Math.floor(((new Date()).getTime() - at.timeStart) / 1000) + at.timeOffset;
if ((at.endTimeSet) && (at.endTimeValue !== null)) {
if (at.time > at.endTimeValue) {
at.stop();
}
else {
at.timeDisplay = at.getTimeDisplay(at.time);
}
}
else {
at.timeDisplay = at.getTimeDisplay(at.time);
}
}, 1000);
};
}]
};
});
})();
Markup
<body>
<angular-timer end-time="10"></angular-timer>
</body>
angular-timer.html
{{at.timeDisplay}}
<br><br>
<button ng-click="at.start()">Start</button>
<br><br>
<button ng-click="at.stop()">Stop</button>
<br><br>
<button ng-click="at.reset()">Reset</button>
<br><br>
<input type="checkbox" ng-model="at.endTimeSet"> End at 27 seconds
I think what you really want is a service that returns a promise, a promise that sends notifications back to your controller. Here's an example of such a service:
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#1.4.0-rc.0" data-semver="1.4.0-rc.0" src="https://code.angularjs.org/1.4.0-rc.0/angular.js"></script>
</head>
<body ng-app="myapp" ng-controller="main">
<h1>Task Retry Example</h1>
<p>Task condition: {{value}}</p>
<button ng-click="value = true">Set task condition to true</button>
<button ng-click="reset()">Reset Task</button>
<p>{{msg}}</p>
<script>
var app = angular.module('myapp', []);
app.controller('main', function($scope, task){
var myTask = function(){
return $scope.value;
}
function success(result){
$scope.msg = result;
}
function failure(reason){
$scope.msg = reason;
}
function notify(value){
$scope.msg = value.message;
}
$scope.reset = function(){
$scope.value = false;
$scope.msg = "Loading...";
task.go(myTask, {maxAttempts: 3, waitTime: 3})
.then(success, failure, notify);
}
$scope.reset();
});
app.service('task', function($q, $timeout){
var DEFAULT_OPTIONS = {
maxAttempts: 1,
waitTime: 10
};
var thisOptions = {};
function _countDownStep(secondsLeft, attemptsLeft, countDownProgress, taskAttemptProgress){
if(secondsLeft <= 0){
countDownProgress.resolve(true);
return;
}
var attempt = thisOptions.maxAttempts - attemptsLeft,
msg = "Attempt failed; retrying (" + attempt + " of " + thisOptions.maxAttempts + ") in " + secondsLeft + " seconds...";
taskAttemptProgress.notify({
"state": "WAITING",
"message": msg
})
$timeout(function(){
_countDownStep(secondsLeft-1, attemptsLeft, countDownProgress, taskAttemptProgress);
}, 1000);
}
function _countDown(secondsLeft, attemptsLeft, progress){
var deferred = $q.defer();
_countDownStep(secondsLeft, attemptsLeft, deferred, progress);
return deferred.promise;
}
function _attempt(task, attemptsLeft, progress){
if(!angular.isFunction(task)) {
progress.reject("Task is not a function.");
return;
}
if(attemptsLeft <= 0){
progress.reject("Max attempts reached.");
}
var result = task();
if(result){
progress.resolve("Successfully completed task.");
}else {
--attemptsLeft;
if(attemptsLeft <= 0){
progress.reject("Max attempts reached.");
}
_countDown(thisOptions.waitTime, attemptsLeft, progress).then(function(){
var attempt = thisOptions.maxAttempts - attemptsLeft,
msg = "Making another attempt (" + attempt + " of " + thisOptions.maxAttempts + ")...";
progress.notify({
"state": "TRYING",
"message": msg
})
_attempt(task, attemptsLeft, progress);
});
}
}
function _go(task, options){
var deferred = $q.defer();
thisOptions = options || DEFAULT_OPTIONS;
_attempt(task, thisOptions.maxAttempts, deferred);
return deferred.promise;
}
return {
go: _go
}
});
</script>