Angular/Ionic和异步SQLite - 确保数据工厂在返回之前初始化
Angular/Ionic和异步SQLite - 确保数据工厂在返回之前初始化
我正在使用Ionic编写一个PhoneGap/Cordova应用程序,并使用SQLite(带有ngCordova)进行持久存储。该应用程序的核心是一个从SQLite数据库检索到的项目滚动列表。
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_; // ...许多SQLite操作 // 级联异步回调来加载数据库并注入虚拟数据 var openDB_ = function(){...}; var createTable_ = function(){...}; // 等等 var getAllItems = function(){ var q = $q.defer(); $cordovaSQLite.execute(db_, sqlSelectString, []).then( function(results) { $log.log("SQL SELECT成功"); 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
最初,我直接返回了工厂。控制器在数据准备好之前运行了getAllItems()
。视图最初为空,只有在返回路由后的第二个getAllItems()
时才显示任何内容。
所以我尝试通过添加一个factoryReady()函数来延迟返回工厂,只有在所有内部数据库操作都准备好之后才调用它
var factoryReady = function(){ return { getAllItems: getAllItems }; };
现在出现了未定义错误,因为在第一次调用时整个工厂不可用,而不仅仅是getAllItems()
返回空。我可以看到SQL数据库在适当的时候被正确写入,但是在此之前Angular会抛出异常。
我现在意识到这是可以预测的,我阅读了AngularJS:使用异步数据初始化服务的帖子,但不太理解如何实现排名第一的答案(由joakimbl提供)。
最好的方法是如何公开服务并确保在内部异步处理完成之前不被控制器调用?我是否需要将整个服务作为一个promise返回,而不仅仅是getAllItems
的结果?我试过这个但是现在很困惑。谢谢。
编辑
我还研究了使用ui-router的resolve
在加载视图时http://blog.brunoscopelliti.com/show-route-only-after-all-promises-are-resolved,但这并不能解决SQL数据/工厂的内部准备问题。如果我返回getAllCases
方法,它仍然会立即被调用,此时SQL数据库中还没有任何数据,SQL查询返回一个空结果集,promise解析并呈现视图。
在这个问题中,出现的原因是在Angular/Ionic应用中使用异步SQLite时,需要确保数据工厂在返回之前被初始化。解决方法是重新编写所有私有方法,使用异步SQL调用返回promises。创建一个公共的initDB方法,将调用链接到私有方法(例如openDB >> dropTable_ >> createTable_等),并返回一个promise(空)。立即返回initDB和getAllItems方法。
在app.js中,使用ui-router的resolve功能。将promise从initDB注入到子状态的resolve对象中,并将resolve对象注入到控制器中。
最后,通过阅读这篇文章(链接在最后),解决了对ui-router的使用问题。
代码如下:
// dataFactory.js .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 }; }]); // app.js .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(); } } }) });
现在一切都正常工作了。非常感谢这篇文章(链接在最后)解决了我对ui-router的使用问题。
参考链接:[Run controllers only after initialization is complete in AngularJS](https://stackoverflow.com/questions/27050496/27050497#27050497)