Ila Ila - 4 months ago 21
Javascript Question

Trying to get an async DB request to work in Angular, "Cannot call method then of undefined"

I'm building a PhoneGap app using AngularJS + an SQLite database. I am having a classic "How does asynchronous work" Angular problem with a database query, getting error "Cannot call method then of undefined". I am hoping someone can help me to see the error of my ways.

Here's my query function. Every alert() in here returns meaningful data indicating that the transaction itself is successful:

.factory('SQLService', ['$q', '$rootScope', 'phonegapReady',
function ($q, $rootScope, phonegapReady) {

function search(query) {
alert("Search running with " + query);

var promise = db.transaction(function(transaction) {

var str = "SELECT category, id, chapter, header, snippet(guidelines, '<b>', '</b>', '...', '-1', '-24' ) AS snip FROM guidelines WHERE content MATCH '" + query + "*';";

transaction.executeSql(str,[], function(transaction, result) {
var resultObj = {},
responses = [];
if (result != null && result.rows != null) {
for (var i = 0; i < result.rows.length; i++) {
resultObj = result.rows.item(i);
alert(resultObj.category); //gives a meaningful value from the DB
responses.push(resultObj);
}
} else {
//default content
}

},defaultNullHandler,defaultErrorHandler);
alert("End of transaction");
});
// Attempting to return the promise to the controller
alert("Return promise"); //this alert happens
return promise;

}

return {
openDB : openDB,
search: search
};
}]);


And in my controller, which gives the "Cannot call method then of undefined" error:

$scope.search = function(query) {

SQLService.search(query).then(function(d) {
console.log("Search THEN"); //never runs
$scope.responses = d; //is never defined
});

}





Thanks to the accepted answer, here is the full working code.

Service



function search(query) {
var deferred = $q.defer();

db.transaction(function(transaction) {

var str = "SELECT category, id, chapter, header, snippet(guidelines, '<b>', '</b>', '...', '-1', '-24' ) AS snip FROM guidelines WHERE content MATCH '" + query + "*';";
transaction.executeSql(str,[], function(transaction, result) {
var resultObj = {},
responses = [];
if (result != null && result.rows != null) {
for (var i = 0; i < result.rows.length; i++) {
resultObj = result.rows.item(i);
responses.push(resultObj);
}
} else {
resultObj.snip = "No results for " + query;
responses.push(resultObj)
}

deferred.resolve(responses); //at the end of processing the responses
},defaultNullHandler,defaultErrorHandler);
});

// Return the promise to the controller
return deferred.promise;

}


Controller



$scope.search = function(query) {

SQLService.search(query).then(function(d) {
$scope.responses = d;
});

}


I can then access the responses in the template using $scope.responses.

Answer

The question here is: what does db.transaction return.

From the way you're using it, I'm guessing it's some 3rd-party code that doesn't return a promise.

Assuming that you're using it correctly (your alert shows the right results), you need to actualy use $q to get the promise working.

Something like this:

function search(query) {
  // Set up the $q deferred object.
  var deferred = $q.defer();

  db.transaction(function(transaction) {
    transaction.executeSql(str, [], function(transaction, result) {
      // do whatever you need to do to the result
      var results = parseDataFrom(result);

      // resolve the promise with the results
      deferred.resolve(results);
    }, nullHandler, errorHandler);
  });

  // Return the deferred's promise.
  return deferred.promise;
}

Now, in your controller, the SQLService.search method will return a promise that should get resolved with the results of your DB call.

Comments