Angular / Ionic and asynchronous SQLite - providing factory data initialized before returning

I am writing a PhoneGap / Cordova application with Ionic and use SQLite (with ngCordova) for permanent storage. The core of the application is a scrollable list of items that are retrieved from the SQLite database.

listController.js

.controller('ListCtrl', [ '$scope', 'dataFactory', function($scope, dataFactory) { var items = dataFactory.getAllItems().then(function(data){ $scope.allItems = data; }); } ]); 

dataFactory.js

 .factory('dataFactory', [function($window, $log, $q, $cordovaSQLite, dummyDataGenerator){ var db_; // ...lots of SQLite fun in here // cascading async callbacks to load the database and inject dummy data var openDB_ = function(){...}; var createTable_ = function(){...}; // etc var getAllItems = function(){ var q = $q.defer(); $cordovaSQLite.execute(db_, sqlSelectString, []).then( function(results) { $log.log("SQL SELECT successful"); var i, len, allItems = []; for(i = 0, len = results.rows.length; i < len; i++) { allItems.push(results.rows.item(i)); } q.resolve(allItems); }, function (err) { q.reject(err); } ); return q.promise; }; return { getAllItems: getAllItems }; ]}); // <-- factory 

Initially, I immediately returned the factory. The controller executed getAllItems() , which was executed before the data was ready. The view was initially empty, only displaying anything upon returning to the route after the second getAllItems()

So, I tried to delay the factory return by adding the factoryReady () function and calling it only after all the internal database files were ready

 var factoryReady = function(){ return { getAllItems: getAllItems }; }; 

And now there is an undefined error, since the whole factory is not available on the first call, and not getAllItems() , which simply returns empty-handed. I see that the SQL database is correctly written over time, but Angular throws an exception before it ends.

Now I understand that this is predictable, I read the AngularJS post : Initializing a service with asynchronous data , but I don’t quite understand how to implement a top-ranked response (by joakimbl)

What is the best way to open a service and ensure that it is not called by the controller until the internal asynchronous operation ends? Do I need to return the ENTIRE service as a promise, and not just the result from getAllItems ? I went for it, but now I'm embarrassed. Thanks.

EDIT

I also studied the use of ui-router resolve when loading the http://blog.brunoscopelliti.com/show-route-only-after-all-promises-are-resolved view, but this does not fix the internal readiness of SQL / factory data. If I return the getAllCases method, it will still be called immediately, there is nothing in the SQL database, the SQL query returns an empty result set, promises to allow and visualize the view.

+8
javascript angularjs sqlite ionic-framework
source share
1 answer

Managed to get it working at the end. Spending this here is for everyone who has a problem.

dataFactory.js

  • Reworked all private methods using async SQL calls in dataFactory.js to return promises
  • A public initDB method has been created that chained calls to private methods (for example, openDB β†’ dropTable_ β†’ createTable_ , etc.). Also returned a promise (empty)
  • initDB returned and getAllItems() from factory immediately

     .factory('dataFactory', [function($window, $log, $q, $cordovaSQLite, dummyDataGenerator){ var db_; // private methods - all return promises var openDB_ = function(dbName){ var q = $q.defer(); // ...call async SQL methods return q.promise; }; var createTable_ = function(){ var q = $q.defer(); // ...call async SQL methods return q.promise; }; // ...etc // public methods var initDB = function(){ var q = $q.defer(); // successively call private methods, chaining to next with .then() openDB_("myDB").then(function(db){ var schema = "...SQL schema here..." dropTable_(db, "FirstTable", schema).then(function(tableName){ // ...etc // when all done, resolve the promise q.resolve(); }) }) return q.promise; } var getAllItems = function(){ var q = $q.defer(); // ...call async SQL methods return q.promise; }; return { initDB: initDB, getAllItems: getAllItems }; ]}); // <-- factory 

app.js

  • Uses resolve ability ui-router
  • My previous attempts were not entered correctly promises
  • Added resolve to top-level abstract state to disable initDB call
  • Promise introduced from initDB to child state resolve object
  • Insert permission object into controller

    // APP ROUTING (using ui-router) .config (function ($ stateProvider, $ urlRouterProvider) {

     $stateProvider // top-level abstract state that houses Ionic side menu & nav .state('app', { url: '/app', abstract: true, templateUrl: "templates/sideMenu.html", resolve: { dbReady: function($log, dataFactory){ // (1) init the DB return dataFactory.initDB().then(function(){ $log.log("initDB promise resolved"); }); } } }) // the following states are all child states of app .state('app.items', { url: "/items", views: { menuContent: { templateUrl: "templates/gbCaseList.html", // (3) now we can inject the items promise into our controller controller: function($scope, $log, items){ // (4) uses resolved items variable injected by ui-router $scope.allItems = items; } } }, resolve: { // (2) note that we MUST inject the dbReady promise, if we don't this will instantiate immediately items: function(dbReady, $log, dataFactory){ // the following call returns a promise return dataFactory.getItems(); } } }) 

Now everything works. Massive thanks to this post for cleaning up my use of ui-router launch controllers only after initialization in AngularJS is complete

+7
source share

All Articles