Ionic/Angular concurrent SQLite connections and writes failing - angularjs

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

Related

Not able to understand $q.all() behavior in AngularJs

I am just trying to export data into excel like this
purchaseRequestService.prototype.export = function (arrID, arrID_A) {
var self = this;
var q = $q.defer();
var tasks = [];
if (arrID.length > 0) {
tasks.push(self.getDataForExportDashboard(arrID));
}
if (arrID_A.length > 0) {
tasks.push(self.getDataForExportDashboard_A(arrID_A));
}
//$q.all(tasks).then(function (res) {
// $q.all([self.getDataForExportDashboard(arrID),self.getDataForExportDashboard_A(arrID_A)]).then(function (res) {
$q.all([ self.getDataForExportDashboard(arrID), self.getDataForExportDashboard_A(arrID_A)]).then(function (res) {
var dt = [];
if (res.length > 1) {
dt.push(res[0][0].concat(res[1][0]));
dt.push(res[0][1].concat(res[1][1]));
} else {
dt.push(res[0][0]);
dt.push(res[0][1]);
}
var q = $q.defer();
ExcelService.exportDashboard(dt).then(function (result) {
q.resolve(result);
}, function (error, status) {
q.reject({
error: result,
status: status
});
});
});
return q.promise;
}
Now its kind of acting weird, sometimes the excel gets downloaded and sometimes not. When I open up debugger tools and stops the code at breakpoint, the download is successful. I am not able to understand what is happening.

Deleting row with scope array leaves blank page

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.

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

Angularjs/Ionic DB result with promise

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..."

How can I show a message counting down every one second with AngularJS?

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>

Resources